10c22197058af98d2aaad87b5f0301c2d5b30d73
[moodle.git] / mod / feedback / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Library of functions and constants for module feedback
19  * includes the main-part of feedback-functions
20  *
21  * @package mod_feedback
22  * @copyright Andreas Grabs
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 // Include forms lib.
29 require_once($CFG->libdir.'/formslib.php');
31 define('FEEDBACK_ANONYMOUS_YES', 1);
32 define('FEEDBACK_ANONYMOUS_NO', 2);
33 define('FEEDBACK_MIN_ANONYMOUS_COUNT_IN_GROUP', 2);
34 define('FEEDBACK_DECIMAL', '.');
35 define('FEEDBACK_THOUSAND', ',');
36 define('FEEDBACK_RESETFORM_RESET', 'feedback_reset_data_');
37 define('FEEDBACK_RESETFORM_DROP', 'feedback_drop_feedback_');
38 define('FEEDBACK_MAX_PIX_LENGTH', '400'); //max. Breite des grafischen Balkens in der Auswertung
39 define('FEEDBACK_DEFAULT_PAGE_COUNT', 20);
41 // Event types.
42 define('FEEDBACK_EVENT_TYPE_OPEN', 'open');
43 define('FEEDBACK_EVENT_TYPE_CLOSE', 'close');
45 /**
46  * @uses FEATURE_GROUPS
47  * @uses FEATURE_GROUPINGS
48  * @uses FEATURE_MOD_INTRO
49  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
50  * @uses FEATURE_GRADE_HAS_GRADE
51  * @uses FEATURE_GRADE_OUTCOMES
52  * @param string $feature FEATURE_xx constant for requested feature
53  * @return mixed True if module supports feature, null if doesn't know
54  */
55 function feedback_supports($feature) {
56     switch($feature) {
57         case FEATURE_GROUPS:                  return true;
58         case FEATURE_GROUPINGS:               return true;
59         case FEATURE_MOD_INTRO:               return true;
60         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
61         case FEATURE_COMPLETION_HAS_RULES:    return true;
62         case FEATURE_GRADE_HAS_GRADE:         return false;
63         case FEATURE_GRADE_OUTCOMES:          return false;
64         case FEATURE_BACKUP_MOODLE2:          return true;
65         case FEATURE_SHOW_DESCRIPTION:        return true;
67         default: return null;
68     }
69 }
71 /**
72  * this will create a new instance and return the id number
73  * of the new instance.
74  *
75  * @global object
76  * @param object $feedback the object given by mod_feedback_mod_form
77  * @return int
78  */
79 function feedback_add_instance($feedback) {
80     global $DB;
82     $feedback->timemodified = time();
83     $feedback->id = '';
85     if (empty($feedback->site_after_submit)) {
86         $feedback->site_after_submit = '';
87     }
89     //saving the feedback in db
90     $feedbackid = $DB->insert_record("feedback", $feedback);
92     $feedback->id = $feedbackid;
94     feedback_set_events($feedback);
96     if (!isset($feedback->coursemodule)) {
97         $cm = get_coursemodule_from_id('feedback', $feedback->id);
98         $feedback->coursemodule = $cm->id;
99     }
100     $context = context_module::instance($feedback->coursemodule);
102     if (!empty($feedback->completionexpected)) {
103         \core_completion\api::update_completion_date_event($feedback->coursemodule, 'feedback', $feedback->id,
104                 $feedback->completionexpected);
105     }
107     $editoroptions = feedback_get_editor_options();
109     // process the custom wysiwyg editor in page_after_submit
110     if ($draftitemid = $feedback->page_after_submit_editor['itemid']) {
111         $feedback->page_after_submit = file_save_draft_area_files($draftitemid, $context->id,
112                                                     'mod_feedback', 'page_after_submit',
113                                                     0, $editoroptions,
114                                                     $feedback->page_after_submit_editor['text']);
116         $feedback->page_after_submitformat = $feedback->page_after_submit_editor['format'];
117     }
118     $DB->update_record('feedback', $feedback);
120     return $feedbackid;
123 /**
124  * this will update a given instance
125  *
126  * @global object
127  * @param object $feedback the object given by mod_feedback_mod_form
128  * @return boolean
129  */
130 function feedback_update_instance($feedback) {
131     global $DB;
133     $feedback->timemodified = time();
134     $feedback->id = $feedback->instance;
136     if (empty($feedback->site_after_submit)) {
137         $feedback->site_after_submit = '';
138     }
140     //save the feedback into the db
141     $DB->update_record("feedback", $feedback);
143     //create or update the new events
144     feedback_set_events($feedback);
145     $completionexpected = (!empty($feedback->completionexpected)) ? $feedback->completionexpected : null;
146     \core_completion\api::update_completion_date_event($feedback->coursemodule, 'feedback', $feedback->id, $completionexpected);
148     $context = context_module::instance($feedback->coursemodule);
150     $editoroptions = feedback_get_editor_options();
152     // process the custom wysiwyg editor in page_after_submit
153     if ($draftitemid = $feedback->page_after_submit_editor['itemid']) {
154         $feedback->page_after_submit = file_save_draft_area_files($draftitemid, $context->id,
155                                                     'mod_feedback', 'page_after_submit',
156                                                     0, $editoroptions,
157                                                     $feedback->page_after_submit_editor['text']);
159         $feedback->page_after_submitformat = $feedback->page_after_submit_editor['format'];
160     }
161     $DB->update_record('feedback', $feedback);
163     return true;
166 /**
167  * Serves the files included in feedback items like label. Implements needed access control ;-)
168  *
169  * There are two situations in general where the files will be sent.
170  * 1) filearea = item, 2) filearea = template
171  *
172  * @package  mod_feedback
173  * @category files
174  * @param stdClass $course course object
175  * @param stdClass $cm course module object
176  * @param stdClass $context context object
177  * @param string $filearea file area
178  * @param array $args extra arguments
179  * @param bool $forcedownload whether or not force download
180  * @param array $options additional options affecting the file serving
181  * @return bool false if file not found, does not return if found - justsend the file
182  */
183 function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
184     global $CFG, $DB;
186     if ($filearea === 'item' or $filearea === 'template') {
187         $itemid = (int)array_shift($args);
188         //get the item what includes the file
189         if (!$item = $DB->get_record('feedback_item', array('id'=>$itemid))) {
190             return false;
191         }
192         $feedbackid = $item->feedback;
193         $templateid = $item->template;
194     }
196     if ($filearea === 'page_after_submit' or $filearea === 'item') {
197         if (! $feedback = $DB->get_record("feedback", array("id"=>$cm->instance))) {
198             return false;
199         }
201         $feedbackid = $feedback->id;
203         //if the filearea is "item" so we check the permissions like view/complete the feedback
204         $canload = false;
205         //first check whether the user has the complete capability
206         if (has_capability('mod/feedback:complete', $context)) {
207             $canload = true;
208         }
210         //now we check whether the user has the view capability
211         if (has_capability('mod/feedback:view', $context)) {
212             $canload = true;
213         }
215         //if the feedback is on frontpage and anonymous and the fullanonymous is allowed
216         //so the file can be loaded too.
217         if (isset($CFG->feedback_allowfullanonymous)
218                     AND $CFG->feedback_allowfullanonymous
219                     AND $course->id == SITEID
220                     AND $feedback->anonymous == FEEDBACK_ANONYMOUS_YES ) {
221             $canload = true;
222         }
224         if (!$canload) {
225             return false;
226         }
227     } else if ($filearea === 'template') { //now we check files in templates
228         if (!$template = $DB->get_record('feedback_template', array('id'=>$templateid))) {
229             return false;
230         }
232         //if the file is not public so the capability edititems has to be there
233         if (!$template->ispublic) {
234             if (!has_capability('mod/feedback:edititems', $context)) {
235                 return false;
236             }
237         } else { //on public templates, at least the user has to be logged in
238             if (!isloggedin()) {
239                 return false;
240             }
241         }
242     } else {
243         return false;
244     }
246     if ($context->contextlevel == CONTEXT_MODULE) {
247         if ($filearea !== 'item' and $filearea !== 'page_after_submit') {
248             return false;
249         }
250     }
252     if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_SYSTEM) {
253         if ($filearea !== 'template') {
254             return false;
255         }
256     }
258     $relativepath = implode('/', $args);
259     if ($filearea === 'page_after_submit') {
260         $fullpath = "/{$context->id}/mod_feedback/$filearea/$relativepath";
261     } else {
262         $fullpath = "/{$context->id}/mod_feedback/$filearea/{$item->id}/$relativepath";
263     }
265     $fs = get_file_storage();
267     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
268         return false;
269     }
271     // finally send the file
272     send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
274     return false;
277 /**
278  * this will delete a given instance.
279  * all referenced data also will be deleted
280  *
281  * @global object
282  * @param int $id the instanceid of feedback
283  * @return boolean
284  */
285 function feedback_delete_instance($id) {
286     global $DB;
288     //get all referenced items
289     $feedbackitems = $DB->get_records('feedback_item', array('feedback'=>$id));
291     //deleting all referenced items and values
292     if (is_array($feedbackitems)) {
293         foreach ($feedbackitems as $feedbackitem) {
294             $DB->delete_records("feedback_value", array("item"=>$feedbackitem->id));
295             $DB->delete_records("feedback_valuetmp", array("item"=>$feedbackitem->id));
296         }
297         if ($delitems = $DB->get_records("feedback_item", array("feedback"=>$id))) {
298             foreach ($delitems as $delitem) {
299                 feedback_delete_item($delitem->id, false);
300             }
301         }
302     }
304     //deleting the completeds
305     $DB->delete_records("feedback_completed", array("feedback"=>$id));
307     //deleting the unfinished completeds
308     $DB->delete_records("feedback_completedtmp", array("feedback"=>$id));
310     //deleting old events
311     $DB->delete_records('event', array('modulename'=>'feedback', 'instance'=>$id));
312     return $DB->delete_records("feedback", array("id"=>$id));
315 /**
316  * Return a small object with summary information about what a
317  * user has done with a given particular instance of this module
318  * Used for user activity reports.
319  * $return->time = the time they did it
320  * $return->info = a short text description
321  *
322  * @param stdClass $course
323  * @param stdClass $user
324  * @param cm_info|stdClass $mod
325  * @param stdClass $feedback
326  * @return stdClass
327  */
328 function feedback_user_outline($course, $user, $mod, $feedback) {
329     global $DB;
330     $outline = (object)['info' => '', 'time' => 0];
331     if ($feedback->anonymous != FEEDBACK_ANONYMOUS_NO) {
332         // Do not disclose any user info if feedback is anonymous.
333         return $outline;
334     }
335     $params = array('userid' => $user->id, 'feedback' => $feedback->id,
336         'anonymous_response' => FEEDBACK_ANONYMOUS_NO);
337     $status = null;
338     $context = context_module::instance($mod->id);
339     if ($completed = $DB->get_record('feedback_completed', $params)) {
340         // User has completed feedback.
341         $outline->info = get_string('completed', 'feedback');
342         $outline->time = $completed->timemodified;
343     } else if ($completedtmp = $DB->get_record('feedback_completedtmp', $params)) {
344         // User has started but not completed feedback.
345         $outline->info = get_string('started', 'feedback');
346         $outline->time = $completedtmp->timemodified;
347     } else if (has_capability('mod/feedback:complete', $context, $user)) {
348         // User has not started feedback but has capability to do so.
349         $outline->info = get_string('not_started', 'feedback');
350     }
352     return $outline;
355 /**
356  * Returns all users who has completed a specified feedback since a given time
357  * many thanks to Manolescu Dorel, who contributed these two functions
358  *
359  * @global object
360  * @global object
361  * @global object
362  * @global object
363  * @uses CONTEXT_MODULE
364  * @param array $activities Passed by reference
365  * @param int $index Passed by reference
366  * @param int $timemodified Timestamp
367  * @param int $courseid
368  * @param int $cmid
369  * @param int $userid
370  * @param int $groupid
371  * @return void
372  */
373 function feedback_get_recent_mod_activity(&$activities, &$index,
374                                           $timemodified, $courseid,
375                                           $cmid, $userid="", $groupid="") {
377     global $CFG, $COURSE, $USER, $DB;
379     if ($COURSE->id == $courseid) {
380         $course = $COURSE;
381     } else {
382         $course = $DB->get_record('course', array('id'=>$courseid));
383     }
385     $modinfo = get_fast_modinfo($course);
387     $cm = $modinfo->cms[$cmid];
389     $sqlargs = array();
391     $userfields = user_picture::fields('u', null, 'useridagain');
392     $sql = " SELECT fk . * , fc . * , $userfields
393                 FROM {feedback_completed} fc
394                     JOIN {feedback} fk ON fk.id = fc.feedback
395                     JOIN {user} u ON u.id = fc.userid ";
397     if ($groupid) {
398         $sql .= " JOIN {groups_members} gm ON  gm.userid=u.id ";
399     }
401     $sql .= " WHERE fc.timemodified > ?
402                 AND fk.id = ?
403                 AND fc.anonymous_response = ?";
404     $sqlargs[] = $timemodified;
405     $sqlargs[] = $cm->instance;
406     $sqlargs[] = FEEDBACK_ANONYMOUS_NO;
408     if ($userid) {
409         $sql .= " AND u.id = ? ";
410         $sqlargs[] = $userid;
411     }
413     if ($groupid) {
414         $sql .= " AND gm.groupid = ? ";
415         $sqlargs[] = $groupid;
416     }
418     if (!$feedbackitems = $DB->get_records_sql($sql, $sqlargs)) {
419         return;
420     }
422     $cm_context = context_module::instance($cm->id);
424     if (!has_capability('mod/feedback:view', $cm_context)) {
425         return;
426     }
428     $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
429     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cm_context);
430     $groupmode       = groups_get_activity_groupmode($cm, $course);
432     $aname = format_string($cm->name, true);
433     foreach ($feedbackitems as $feedbackitem) {
434         if ($feedbackitem->userid != $USER->id) {
436             if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
437                 $usersgroups = groups_get_all_groups($course->id,
438                                                      $feedbackitem->userid,
439                                                      $cm->groupingid);
440                 if (!is_array($usersgroups)) {
441                     continue;
442                 }
443                 $usersgroups = array_keys($usersgroups);
444                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
445                 if (empty($intersect)) {
446                     continue;
447                 }
448             }
449         }
451         $tmpactivity = new stdClass();
453         $tmpactivity->type      = 'feedback';
454         $tmpactivity->cmid      = $cm->id;
455         $tmpactivity->name      = $aname;
456         $tmpactivity->sectionnum= $cm->sectionnum;
457         $tmpactivity->timestamp = $feedbackitem->timemodified;
459         $tmpactivity->content = new stdClass();
460         $tmpactivity->content->feedbackid = $feedbackitem->id;
461         $tmpactivity->content->feedbackuserid = $feedbackitem->userid;
463         $tmpactivity->user = user_picture::unalias($feedbackitem, null, 'useridagain');
464         $tmpactivity->user->fullname = fullname($feedbackitem, $viewfullnames);
466         $activities[$index++] = $tmpactivity;
467     }
469     return;
472 /**
473  * Prints all users who has completed a specified feedback since a given time
474  * many thanks to Manolescu Dorel, who contributed these two functions
475  *
476  * @global object
477  * @param object $activity
478  * @param int $courseid
479  * @param string $detail
480  * @param array $modnames
481  * @return void Output is echo'd
482  */
483 function feedback_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
484     global $CFG, $OUTPUT;
486     echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
488     echo "<tr><td class=\"userpicture\" valign=\"top\">";
489     echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
490     echo "</td><td>";
492     if ($detail) {
493         $modname = $modnames[$activity->type];
494         echo '<div class="title">';
495         echo $OUTPUT->image_icon('icon', $modname, $activity->type);
496         echo "<a href=\"$CFG->wwwroot/mod/feedback/view.php?id={$activity->cmid}\">{$activity->name}</a>";
497         echo '</div>';
498     }
500     echo '<div class="title">';
501     echo '</div>';
503     echo '<div class="user">';
504     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
505          ."{$activity->user->fullname}</a> - ".userdate($activity->timestamp);
506     echo '</div>';
508     echo "</td></tr></table>";
510     return;
513 /**
514  * Obtains the automatic completion state for this feedback based on the condition
515  * in feedback settings.
516  *
517  * @param object $course Course
518  * @param object $cm Course-module
519  * @param int $userid User ID
520  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
521  * @return bool True if completed, false if not, $type if conditions not set.
522  */
523 function feedback_get_completion_state($course, $cm, $userid, $type) {
524     global $CFG, $DB;
526     // Get feedback details
527     $feedback = $DB->get_record('feedback', array('id'=>$cm->instance), '*', MUST_EXIST);
529     // If completion option is enabled, evaluate it and return true/false
530     if ($feedback->completionsubmit) {
531         $params = array('userid'=>$userid, 'feedback'=>$feedback->id);
532         return $DB->record_exists('feedback_completed', $params);
533     } else {
534         // Completion option is not enabled so just return $type
535         return $type;
536     }
539 /**
540  * Print a detailed representation of what a  user has done with
541  * a given particular instance of this module, for user activity reports.
542  *
543  * @param stdClass $course
544  * @param stdClass $user
545  * @param cm_info|stdClass $mod
546  * @param stdClass $feedback
547  */
548 function feedback_user_complete($course, $user, $mod, $feedback) {
549     global $DB;
550     if ($feedback->anonymous != FEEDBACK_ANONYMOUS_NO) {
551         // Do not disclose any user info if feedback is anonymous.
552         return;
553     }
554     $params = array('userid' => $user->id, 'feedback' => $feedback->id,
555         'anonymous_response' => FEEDBACK_ANONYMOUS_NO);
556     $url = $status = null;
557     $context = context_module::instance($mod->id);
558     if ($completed = $DB->get_record('feedback_completed', $params)) {
559         // User has completed feedback.
560         if (has_capability('mod/feedback:viewreports', $context)) {
561             $url = new moodle_url('/mod/feedback/show_entries.php',
562                 ['id' => $mod->id, 'userid' => $user->id,
563                     'showcompleted' => $completed->id]);
564         }
565         $status = get_string('completedon', 'feedback', userdate($completed->timemodified));
566     } else if ($completedtmp = $DB->get_record('feedback_completedtmp', $params)) {
567         // User has started but not completed feedback.
568         $status = get_string('startedon', 'feedback', userdate($completedtmp->timemodified));
569     } else if (has_capability('mod/feedback:complete', $context, $user)) {
570         // User has not started feedback but has capability to do so.
571         $status = get_string('not_started', 'feedback');
572     }
574     if ($url && $status) {
575         echo html_writer::link($url, $status);
576     } else if ($status) {
577         echo html_writer::div($status);
578     }
581 /**
582  * @return bool true
583  */
584 function feedback_cron () {
585     return true;
588 /**
589  * @return bool false
590  */
591 function feedback_scale_used ($feedbackid, $scaleid) {
592     return false;
595 /**
596  * Checks if scale is being used by any instance of feedback
597  *
598  * This is used to find out if scale used anywhere
599  * @param $scaleid int
600  * @return boolean True if the scale is used by any assignment
601  */
602 function feedback_scale_used_anywhere($scaleid) {
603     return false;
606 /**
607  * List the actions that correspond to a view of this module.
608  * This is used by the participation report.
609  *
610  * Note: This is not used by new logging system. Event with
611  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
612  *       be considered as view action.
613  *
614  * @return array
615  */
616 function feedback_get_view_actions() {
617     return array('view', 'view all');
620 /**
621  * List the actions that correspond to a post of this module.
622  * This is used by the participation report.
623  *
624  * Note: This is not used by new logging system. Event with
625  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
626  *       will be considered as post action.
627  *
628  * @return array
629  */
630 function feedback_get_post_actions() {
631     return array('submit');
634 /**
635  * This function is used by the reset_course_userdata function in moodlelib.
636  * This function will remove all responses from the specified feedback
637  * and clean up any related data.
638  *
639  * @global object
640  * @global object
641  * @uses FEEDBACK_RESETFORM_RESET
642  * @uses FEEDBACK_RESETFORM_DROP
643  * @param object $data the data submitted from the reset course.
644  * @return array status array
645  */
646 function feedback_reset_userdata($data) {
647     global $CFG, $DB;
649     $resetfeedbacks = array();
650     $dropfeedbacks = array();
651     $status = array();
652     $componentstr = get_string('modulenameplural', 'feedback');
654     //get the relevant entries from $data
655     foreach ($data as $key => $value) {
656         switch(true) {
657             case substr($key, 0, strlen(FEEDBACK_RESETFORM_RESET)) == FEEDBACK_RESETFORM_RESET:
658                 if ($value == 1) {
659                     $templist = explode('_', $key);
660                     if (isset($templist[3])) {
661                         $resetfeedbacks[] = intval($templist[3]);
662                     }
663                 }
664             break;
665             case substr($key, 0, strlen(FEEDBACK_RESETFORM_DROP)) == FEEDBACK_RESETFORM_DROP:
666                 if ($value == 1) {
667                     $templist = explode('_', $key);
668                     if (isset($templist[3])) {
669                         $dropfeedbacks[] = intval($templist[3]);
670                     }
671                 }
672             break;
673         }
674     }
676     //reset the selected feedbacks
677     foreach ($resetfeedbacks as $id) {
678         $feedback = $DB->get_record('feedback', array('id'=>$id));
679         feedback_delete_all_completeds($feedback);
680         $status[] = array('component'=>$componentstr.':'.$feedback->name,
681                         'item'=>get_string('resetting_data', 'feedback'),
682                         'error'=>false);
683     }
685     // Updating dates - shift may be negative too.
686     if ($data->timeshift) {
687         // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
688         // See MDL-9367.
689         $shifterror = !shift_course_mod_dates('feedback', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid);
690         $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => $shifterror);
691     }
693     return $status;
696 /**
697  * Called by course/reset.php
698  *
699  * @global object
700  * @uses FEEDBACK_RESETFORM_RESET
701  * @param object $mform form passed by reference
702  */
703 function feedback_reset_course_form_definition(&$mform) {
704     global $COURSE, $DB;
706     $mform->addElement('header', 'feedbackheader', get_string('modulenameplural', 'feedback'));
708     if (!$feedbacks = $DB->get_records('feedback', array('course'=>$COURSE->id), 'name')) {
709         return;
710     }
712     $mform->addElement('static', 'hint', get_string('resetting_data', 'feedback'));
713     foreach ($feedbacks as $feedback) {
714         $mform->addElement('checkbox', FEEDBACK_RESETFORM_RESET.$feedback->id, $feedback->name);
715     }
718 /**
719  * Course reset form defaults.
720  *
721  * @global object
722  * @uses FEEDBACK_RESETFORM_RESET
723  * @param object $course
724  */
725 function feedback_reset_course_form_defaults($course) {
726     global $DB;
728     $return = array();
729     if (!$feedbacks = $DB->get_records('feedback', array('course'=>$course->id), 'name')) {
730         return;
731     }
732     foreach ($feedbacks as $feedback) {
733         $return[FEEDBACK_RESETFORM_RESET.$feedback->id] = true;
734     }
735     return $return;
738 /**
739  * Called by course/reset.php and shows the formdata by coursereset.
740  * it prints checkboxes for each feedback available at the given course
741  * there are two checkboxes:
742  * 1) delete userdata and keep the feedback
743  * 2) delete userdata and drop the feedback
744  *
745  * @global object
746  * @uses FEEDBACK_RESETFORM_RESET
747  * @uses FEEDBACK_RESETFORM_DROP
748  * @param object $course
749  * @return void
750  */
751 function feedback_reset_course_form($course) {
752     global $DB, $OUTPUT;
754     echo get_string('resetting_feedbacks', 'feedback'); echo ':<br />';
755     if (!$feedbacks = $DB->get_records('feedback', array('course'=>$course->id), 'name')) {
756         return;
757     }
759     foreach ($feedbacks as $feedback) {
760         echo '<p>';
761         echo get_string('name', 'feedback').': '.$feedback->name.'<br />';
762         echo html_writer::checkbox(FEEDBACK_RESETFORM_RESET.$feedback->id,
763                                 1, true,
764                                 get_string('resetting_data', 'feedback'));
765         echo '<br />';
766         echo html_writer::checkbox(FEEDBACK_RESETFORM_DROP.$feedback->id,
767                                 1, false,
768                                 get_string('drop_feedback', 'feedback'));
769         echo '</p>';
770     }
773 /**
774  * This gets an array with default options for the editor
775  *
776  * @return array the options
777  */
778 function feedback_get_editor_options() {
779     return array('maxfiles' => EDITOR_UNLIMITED_FILES,
780                 'trusttext'=>true);
783 /**
784  * This creates new events given as timeopen and closeopen by $feedback.
785  *
786  * @global object
787  * @param object $feedback
788  * @return void
789  */
790 function feedback_set_events($feedback) {
791     global $DB, $CFG;
793     // Include calendar/lib.php.
794     require_once($CFG->dirroot.'/calendar/lib.php');
796     // Get CMID if not sent as part of $feedback.
797     if (!isset($feedback->coursemodule)) {
798         $cm = get_coursemodule_from_instance('feedback', $feedback->id, $feedback->course);
799         $feedback->coursemodule = $cm->id;
800     }
802     // Feedback start calendar events.
803     $eventid = $DB->get_field('event', 'id',
804             array('modulename' => 'feedback', 'instance' => $feedback->id, 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN));
806     if (isset($feedback->timeopen) && $feedback->timeopen > 0) {
807         $event = new stdClass();
808         $event->eventtype    = FEEDBACK_EVENT_TYPE_OPEN;
809         $event->type         = empty($feedback->timeclose) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
810         $event->name         = get_string('calendarstart', 'feedback', $feedback->name);
811         $event->description  = format_module_intro('feedback', $feedback, $feedback->coursemodule);
812         $event->timestart    = $feedback->timeopen;
813         $event->timesort     = $feedback->timeopen;
814         $event->visible      = instance_is_visible('feedback', $feedback);
815         $event->timeduration = 0;
816         if ($eventid) {
817             // Calendar event exists so update it.
818             $event->id = $eventid;
819             $calendarevent = calendar_event::load($event->id);
820             $calendarevent->update($event, false);
821         } else {
822             // Event doesn't exist so create one.
823             $event->courseid     = $feedback->course;
824             $event->groupid      = 0;
825             $event->userid       = 0;
826             $event->modulename   = 'feedback';
827             $event->instance     = $feedback->id;
828             $event->eventtype    = FEEDBACK_EVENT_TYPE_OPEN;
829             calendar_event::create($event, false);
830         }
831     } else if ($eventid) {
832         // Calendar event is on longer needed.
833         $calendarevent = calendar_event::load($eventid);
834         $calendarevent->delete();
835     }
837     // Feedback close calendar events.
838     $eventid = $DB->get_field('event', 'id',
839             array('modulename' => 'feedback', 'instance' => $feedback->id, 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE));
841     if (isset($feedback->timeclose) && $feedback->timeclose > 0) {
842         $event = new stdClass();
843         $event->type         = CALENDAR_EVENT_TYPE_ACTION;
844         $event->eventtype    = FEEDBACK_EVENT_TYPE_CLOSE;
845         $event->name         = get_string('calendarend', 'feedback', $feedback->name);
846         $event->description  = format_module_intro('feedback', $feedback, $feedback->coursemodule);
847         $event->timestart    = $feedback->timeclose;
848         $event->timesort     = $feedback->timeclose;
849         $event->visible      = instance_is_visible('feedback', $feedback);
850         $event->timeduration = 0;
851         if ($eventid) {
852             // Calendar event exists so update it.
853             $event->id = $eventid;
854             $calendarevent = calendar_event::load($event->id);
855             $calendarevent->update($event, false);
856         } else {
857             // Event doesn't exist so create one.
858             $event->courseid     = $feedback->course;
859             $event->groupid      = 0;
860             $event->userid       = 0;
861             $event->modulename   = 'feedback';
862             $event->instance     = $feedback->id;
863             calendar_event::create($event, false);
864         }
865     } else if ($eventid) {
866         // Calendar event is on longer needed.
867         $calendarevent = calendar_event::load($eventid);
868         $calendarevent->delete();
869     }
872 /**
873  * This standard function will check all instances of this module
874  * and make sure there are up-to-date events created for each of them.
875  * If courseid = 0, then every feedback event in the site is checked, else
876  * only feedback events belonging to the course specified are checked.
877  * This function is used, in its new format, by restore_refresh_events()
878  *
879  * @param int $courseid
880  * @param int|stdClass $instance Feedback module instance or ID.
881  * @param int|stdClass $cm Course module object or ID (not used in this module).
882  * @return bool
883  */
884 function feedback_refresh_events($courseid = 0, $instance = null, $cm = null) {
885     global $DB;
887     // If we have instance information then we can just update the one event instead of updating all events.
888     if (isset($instance)) {
889         if (!is_object($instance)) {
890             $instance = $DB->get_record('feedback', array('id' => $instance), '*', MUST_EXIST);
891         }
892         feedback_set_events($instance);
893         return true;
894     }
896     if ($courseid) {
897         if (! $feedbacks = $DB->get_records("feedback", array("course" => $courseid))) {
898             return true;
899         }
900     } else {
901         if (! $feedbacks = $DB->get_records("feedback")) {
902             return true;
903         }
904     }
906     foreach ($feedbacks as $feedback) {
907         feedback_set_events($feedback);
908     }
909     return true;
912 /**
913  * this function is called by {@link feedback_delete_userdata()}
914  * it drops the feedback-instance from the course_module table
915  *
916  * @global object
917  * @param int $id the id from the coursemodule
918  * @return boolean
919  */
920 function feedback_delete_course_module($id) {
921     global $DB;
923     if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
924         return true;
925     }
926     return $DB->delete_records('course_modules', array('id'=>$cm->id));
931 ////////////////////////////////////////////////
932 //functions to handle capabilities
933 ////////////////////////////////////////////////
935 /**
936  * @deprecated since 3.1
937  */
938 function feedback_get_context() {
939     throw new coding_exception('feedback_get_context() can not be used anymore.');
942 /**
943  *  returns true if the current role is faked by switching role feature
944  *
945  * @global object
946  * @return boolean
947  */
948 function feedback_check_is_switchrole() {
949     global $USER;
950     if (isset($USER->switchrole) AND
951             is_array($USER->switchrole) AND
952             count($USER->switchrole) > 0) {
954         return true;
955     }
956     return false;
959 /**
960  * count users which have not completed the feedback
961  *
962  * @global object
963  * @uses CONTEXT_MODULE
964  * @param cm_info $cm Course-module object
965  * @param int $group single groupid
966  * @param string $sort
967  * @param int $startpage
968  * @param int $pagecount
969  * @param bool $includestatus to return if the user started or not the feedback among the complete user record
970  * @return array array of user ids or user objects when $includestatus set to true
971  */
972 function feedback_get_incomplete_users(cm_info $cm,
973                                        $group = false,
974                                        $sort = '',
975                                        $startpage = false,
976                                        $pagecount = false,
977                                        $includestatus = false) {
979     global $DB;
981     $context = context_module::instance($cm->id);
983     //first get all user who can complete this feedback
984     $cap = 'mod/feedback:complete';
985     $allnames = get_all_user_name_fields(true, 'u');
986     $fields = 'u.id, ' . $allnames . ', u.picture, u.email, u.imagealt';
987     if (!$allusers = get_users_by_capability($context,
988                                             $cap,
989                                             $fields,
990                                             $sort,
991                                             '',
992                                             '',
993                                             $group,
994                                             '',
995                                             true)) {
996         return false;
997     }
998     // Filter users that are not in the correct group/grouping.
999     $info = new \core_availability\info_module($cm);
1000     $allusersrecords = $info->filter_user_list($allusers);
1002     $allusers = array_keys($allusersrecords);
1004     //now get all completeds
1005     $params = array('feedback'=>$cm->instance);
1006     if ($completedusers = $DB->get_records_menu('feedback_completed', $params, '', 'id, userid')) {
1007         // Now strike all completedusers from allusers.
1008         $allusers = array_diff($allusers, $completedusers);
1009     }
1011     //for paging I use array_slice()
1012     if ($startpage !== false AND $pagecount !== false) {
1013         $allusers = array_slice($allusers, $startpage, $pagecount);
1014     }
1016     // Check if we should return the full users objects.
1017     if ($includestatus) {
1018         $userrecords = [];
1019         $startedusers = $DB->get_records_menu('feedback_completedtmp', ['feedback' => $cm->instance], '', 'id, userid');
1020         $startedusers = array_flip($startedusers);
1021         foreach ($allusers as $userid) {
1022             $allusersrecords[$userid]->feedbackstarted = isset($startedusers[$userid]);
1023             $userrecords[] = $allusersrecords[$userid];
1024         }
1025         return $userrecords;
1026     } else {    // Return just user ids.
1027         return $allusers;
1028     }
1031 /**
1032  * count users which have not completed the feedback
1033  *
1034  * @global object
1035  * @param object $cm
1036  * @param int $group single groupid
1037  * @return int count of userrecords
1038  */
1039 function feedback_count_incomplete_users($cm, $group = false) {
1040     if ($allusers = feedback_get_incomplete_users($cm, $group)) {
1041         return count($allusers);
1042     }
1043     return 0;
1046 /**
1047  * count users which have completed a feedback
1048  *
1049  * @global object
1050  * @uses FEEDBACK_ANONYMOUS_NO
1051  * @param object $cm
1052  * @param int $group single groupid
1053  * @return int count of userrecords
1054  */
1055 function feedback_count_complete_users($cm, $group = false) {
1056     global $DB;
1058     $params = array(FEEDBACK_ANONYMOUS_NO, $cm->instance);
1060     $fromgroup = '';
1061     $wheregroup = '';
1062     if ($group) {
1063         $fromgroup = ', {groups_members} g';
1064         $wheregroup = ' AND g.groupid = ? AND g.userid = c.userid';
1065         $params[] = $group;
1066     }
1068     $sql = 'SELECT COUNT(u.id) FROM {user} u, {feedback_completed} c'.$fromgroup.'
1069               WHERE anonymous_response = ? AND u.id = c.userid AND c.feedback = ?
1070               '.$wheregroup;
1072     return $DB->count_records_sql($sql, $params);
1076 /**
1077  * get users which have completed a feedback
1078  *
1079  * @global object
1080  * @uses CONTEXT_MODULE
1081  * @uses FEEDBACK_ANONYMOUS_NO
1082  * @param object $cm
1083  * @param int $group single groupid
1084  * @param string $where a sql where condition (must end with " AND ")
1085  * @param array parameters used in $where
1086  * @param string $sort a table field
1087  * @param int $startpage
1088  * @param int $pagecount
1089  * @return object the userrecords
1090  */
1091 function feedback_get_complete_users($cm,
1092                                      $group = false,
1093                                      $where = '',
1094                                      array $params = null,
1095                                      $sort = '',
1096                                      $startpage = false,
1097                                      $pagecount = false) {
1099     global $DB;
1101     $context = context_module::instance($cm->id);
1103     $params = (array)$params;
1105     $params['anon'] = FEEDBACK_ANONYMOUS_NO;
1106     $params['instance'] = $cm->instance;
1108     $fromgroup = '';
1109     $wheregroup = '';
1110     if ($group) {
1111         $fromgroup = ', {groups_members} g';
1112         $wheregroup = ' AND g.groupid = :group AND g.userid = c.userid';
1113         $params['group'] = $group;
1114     }
1116     if ($sort) {
1117         $sortsql = ' ORDER BY '.$sort;
1118     } else {
1119         $sortsql = '';
1120     }
1122     $ufields = user_picture::fields('u');
1123     $sql = 'SELECT DISTINCT '.$ufields.', c.timemodified as completed_timemodified
1124             FROM {user} u, {feedback_completed} c '.$fromgroup.'
1125             WHERE '.$where.' anonymous_response = :anon
1126                 AND u.id = c.userid
1127                 AND c.feedback = :instance
1128               '.$wheregroup.$sortsql;
1130     if ($startpage === false OR $pagecount === false) {
1131         $startpage = false;
1132         $pagecount = false;
1133     }
1134     return $DB->get_records_sql($sql, $params, $startpage, $pagecount);
1137 /**
1138  * get users which have the viewreports-capability
1139  *
1140  * @uses CONTEXT_MODULE
1141  * @param int $cmid
1142  * @param mixed $groups single groupid or array of groupids - group(s) user is in
1143  * @return object the userrecords
1144  */
1145 function feedback_get_viewreports_users($cmid, $groups = false) {
1147     $context = context_module::instance($cmid);
1149     //description of the call below:
1150     //get_users_by_capability($context, $capability, $fields='', $sort='', $limitfrom='',
1151     //                          $limitnum='', $groups='', $exceptions='', $doanything=true)
1152     return get_users_by_capability($context,
1153                             'mod/feedback:viewreports',
1154                             '',
1155                             'lastname',
1156                             '',
1157                             '',
1158                             $groups,
1159                             '',
1160                             false);
1163 /**
1164  * get users which have the receivemail-capability
1165  *
1166  * @uses CONTEXT_MODULE
1167  * @param int $cmid
1168  * @param mixed $groups single groupid or array of groupids - group(s) user is in
1169  * @return object the userrecords
1170  */
1171 function feedback_get_receivemail_users($cmid, $groups = false) {
1173     $context = context_module::instance($cmid);
1175     //description of the call below:
1176     //get_users_by_capability($context, $capability, $fields='', $sort='', $limitfrom='',
1177     //                          $limitnum='', $groups='', $exceptions='', $doanything=true)
1178     return get_users_by_capability($context,
1179                             'mod/feedback:receivemail',
1180                             '',
1181                             'lastname',
1182                             '',
1183                             '',
1184                             $groups,
1185                             '',
1186                             false);
1189 ////////////////////////////////////////////////
1190 //functions to handle the templates
1191 ////////////////////////////////////////////////
1192 ////////////////////////////////////////////////
1194 /**
1195  * creates a new template-record.
1196  *
1197  * @global object
1198  * @param int $courseid
1199  * @param string $name the name of template shown in the templatelist
1200  * @param int $ispublic 0:privat 1:public
1201  * @return int the new templateid
1202  */
1203 function feedback_create_template($courseid, $name, $ispublic = 0) {
1204     global $DB;
1206     $templ = new stdClass();
1207     $templ->course   = ($ispublic ? 0 : $courseid);
1208     $templ->name     = $name;
1209     $templ->ispublic = $ispublic;
1211     $templid = $DB->insert_record('feedback_template', $templ);
1212     return $DB->get_record('feedback_template', array('id'=>$templid));
1215 /**
1216  * creates new template items.
1217  * all items will be copied and the attribute feedback will be set to 0
1218  * and the attribute template will be set to the new templateid
1219  *
1220  * @global object
1221  * @uses CONTEXT_MODULE
1222  * @uses CONTEXT_COURSE
1223  * @param object $feedback
1224  * @param string $name the name of template shown in the templatelist
1225  * @param int $ispublic 0:privat 1:public
1226  * @return boolean
1227  */
1228 function feedback_save_as_template($feedback, $name, $ispublic = 0) {
1229     global $DB;
1230     $fs = get_file_storage();
1232     if (!$feedbackitems = $DB->get_records('feedback_item', array('feedback'=>$feedback->id))) {
1233         return false;
1234     }
1236     if (!$newtempl = feedback_create_template($feedback->course, $name, $ispublic)) {
1237         return false;
1238     }
1240     //files in the template_item are in the context of the current course or
1241     //if the template is public the files are in the system context
1242     //files in the feedback_item are in the feedback_context of the feedback
1243     if ($ispublic) {
1244         $s_context = context_system::instance();
1245     } else {
1246         $s_context = context_course::instance($newtempl->course);
1247     }
1248     $cm = get_coursemodule_from_instance('feedback', $feedback->id);
1249     $f_context = context_module::instance($cm->id);
1251     //create items of this new template
1252     //depend items we are storing temporary in an mapping list array(new id => dependitem)
1253     //we also store a mapping of all items array(oldid => newid)
1254     $dependitemsmap = array();
1255     $itembackup = array();
1256     foreach ($feedbackitems as $item) {
1258         $t_item = clone($item);
1260         unset($t_item->id);
1261         $t_item->feedback = 0;
1262         $t_item->template     = $newtempl->id;
1263         $t_item->id = $DB->insert_record('feedback_item', $t_item);
1264         //copy all included files to the feedback_template filearea
1265         $itemfiles = $fs->get_area_files($f_context->id,
1266                                     'mod_feedback',
1267                                     'item',
1268                                     $item->id,
1269                                     "id",
1270                                     false);
1271         if ($itemfiles) {
1272             foreach ($itemfiles as $ifile) {
1273                 $file_record = new stdClass();
1274                 $file_record->contextid = $s_context->id;
1275                 $file_record->component = 'mod_feedback';
1276                 $file_record->filearea = 'template';
1277                 $file_record->itemid = $t_item->id;
1278                 $fs->create_file_from_storedfile($file_record, $ifile);
1279             }
1280         }
1282         $itembackup[$item->id] = $t_item->id;
1283         if ($t_item->dependitem) {
1284             $dependitemsmap[$t_item->id] = $t_item->dependitem;
1285         }
1287     }
1289     //remapping the dependency
1290     foreach ($dependitemsmap as $key => $dependitem) {
1291         $newitem = $DB->get_record('feedback_item', array('id'=>$key));
1292         $newitem->dependitem = $itembackup[$newitem->dependitem];
1293         $DB->update_record('feedback_item', $newitem);
1294     }
1296     return true;
1299 /**
1300  * deletes all feedback_items related to the given template id
1301  *
1302  * @global object
1303  * @uses CONTEXT_COURSE
1304  * @param object $template the template
1305  * @return void
1306  */
1307 function feedback_delete_template($template) {
1308     global $DB;
1310     //deleting the files from the item is done by feedback_delete_item
1311     if ($t_items = $DB->get_records("feedback_item", array("template"=>$template->id))) {
1312         foreach ($t_items as $t_item) {
1313             feedback_delete_item($t_item->id, false, $template);
1314         }
1315     }
1316     $DB->delete_records("feedback_template", array("id"=>$template->id));
1319 /**
1320  * creates new feedback_item-records from template.
1321  * if $deleteold is set true so the existing items of the given feedback will be deleted
1322  * if $deleteold is set false so the new items will be appanded to the old items
1323  *
1324  * @global object
1325  * @uses CONTEXT_COURSE
1326  * @uses CONTEXT_MODULE
1327  * @param object $feedback
1328  * @param int $templateid
1329  * @param boolean $deleteold
1330  */
1331 function feedback_items_from_template($feedback, $templateid, $deleteold = false) {
1332     global $DB, $CFG;
1334     require_once($CFG->libdir.'/completionlib.php');
1336     $fs = get_file_storage();
1338     if (!$template = $DB->get_record('feedback_template', array('id'=>$templateid))) {
1339         return false;
1340     }
1341     //get all templateitems
1342     if (!$templitems = $DB->get_records('feedback_item', array('template'=>$templateid))) {
1343         return false;
1344     }
1346     //files in the template_item are in the context of the current course
1347     //files in the feedback_item are in the feedback_context of the feedback
1348     if ($template->ispublic) {
1349         $s_context = context_system::instance();
1350     } else {
1351         $s_context = context_course::instance($feedback->course);
1352     }
1353     $course = $DB->get_record('course', array('id'=>$feedback->course));
1354     $cm = get_coursemodule_from_instance('feedback', $feedback->id);
1355     $f_context = context_module::instance($cm->id);
1357     //if deleteold then delete all old items before
1358     //get all items
1359     if ($deleteold) {
1360         if ($feedbackitems = $DB->get_records('feedback_item', array('feedback'=>$feedback->id))) {
1361             //delete all items of this feedback
1362             foreach ($feedbackitems as $item) {
1363                 feedback_delete_item($item->id, false);
1364             }
1366             $params = array('feedback'=>$feedback->id);
1367             if ($completeds = $DB->get_records('feedback_completed', $params)) {
1368                 $completion = new completion_info($course);
1369                 foreach ($completeds as $completed) {
1370                     $DB->delete_records('feedback_completed', array('id' => $completed->id));
1371                     // Update completion state
1372                     if ($completion->is_enabled($cm) && $cm->completion == COMPLETION_TRACKING_AUTOMATIC &&
1373                             $feedback->completionsubmit) {
1374                         $completion->update_state($cm, COMPLETION_INCOMPLETE, $completed->userid);
1375                     }
1376                 }
1377             }
1378             $DB->delete_records('feedback_completedtmp', array('feedback'=>$feedback->id));
1379         }
1380         $positionoffset = 0;
1381     } else {
1382         //if the old items are kept the new items will be appended
1383         //therefor the new position has an offset
1384         $positionoffset = $DB->count_records('feedback_item', array('feedback'=>$feedback->id));
1385     }
1387     //create items of this new template
1388     //depend items we are storing temporary in an mapping list array(new id => dependitem)
1389     //we also store a mapping of all items array(oldid => newid)
1390     $dependitemsmap = array();
1391     $itembackup = array();
1392     foreach ($templitems as $t_item) {
1393         $item = clone($t_item);
1394         unset($item->id);
1395         $item->feedback = $feedback->id;
1396         $item->template = 0;
1397         $item->position = $item->position + $positionoffset;
1399         $item->id = $DB->insert_record('feedback_item', $item);
1401         //moving the files to the new item
1402         $templatefiles = $fs->get_area_files($s_context->id,
1403                                         'mod_feedback',
1404                                         'template',
1405                                         $t_item->id,
1406                                         "id",
1407                                         false);
1408         if ($templatefiles) {
1409             foreach ($templatefiles as $tfile) {
1410                 $file_record = new stdClass();
1411                 $file_record->contextid = $f_context->id;
1412                 $file_record->component = 'mod_feedback';
1413                 $file_record->filearea = 'item';
1414                 $file_record->itemid = $item->id;
1415                 $fs->create_file_from_storedfile($file_record, $tfile);
1416             }
1417         }
1419         $itembackup[$t_item->id] = $item->id;
1420         if ($item->dependitem) {
1421             $dependitemsmap[$item->id] = $item->dependitem;
1422         }
1423     }
1425     //remapping the dependency
1426     foreach ($dependitemsmap as $key => $dependitem) {
1427         $newitem = $DB->get_record('feedback_item', array('id'=>$key));
1428         $newitem->dependitem = $itembackup[$newitem->dependitem];
1429         $DB->update_record('feedback_item', $newitem);
1430     }
1433 /**
1434  * get the list of available templates.
1435  * if the $onlyown param is set true so only templates from own course will be served
1436  * this is important for droping templates
1437  *
1438  * @global object
1439  * @param object $course
1440  * @param string $onlyownorpublic
1441  * @return array the template recordsets
1442  */
1443 function feedback_get_template_list($course, $onlyownorpublic = '') {
1444     global $DB, $CFG;
1446     switch($onlyownorpublic) {
1447         case '':
1448             $templates = $DB->get_records_select('feedback_template',
1449                                                  'course = ? OR ispublic = 1',
1450                                                  array($course->id),
1451                                                  'name');
1452             break;
1453         case 'own':
1454             $templates = $DB->get_records('feedback_template',
1455                                           array('course'=>$course->id),
1456                                           'name');
1457             break;
1458         case 'public':
1459             $templates = $DB->get_records('feedback_template', array('ispublic'=>1), 'name');
1460             break;
1461     }
1462     return $templates;
1465 ////////////////////////////////////////////////
1466 //Handling der Items
1467 ////////////////////////////////////////////////
1468 ////////////////////////////////////////////////
1470 /**
1471  * load the lib.php from item-plugin-dir and returns the instance of the itemclass
1472  *
1473  * @param string $typ
1474  * @return feedback_item_base the instance of itemclass
1475  */
1476 function feedback_get_item_class($typ) {
1477     global $CFG;
1479     //get the class of item-typ
1480     $itemclass = 'feedback_item_'.$typ;
1481     //get the instance of item-class
1482     if (!class_exists($itemclass)) {
1483         require_once($CFG->dirroot.'/mod/feedback/item/'.$typ.'/lib.php');
1484     }
1485     return new $itemclass();
1488 /**
1489  * load the available item plugins from given subdirectory of $CFG->dirroot
1490  * the default is "mod/feedback/item"
1491  *
1492  * @global object
1493  * @param string $dir the subdir
1494  * @return array pluginnames as string
1495  */
1496 function feedback_load_feedback_items($dir = 'mod/feedback/item') {
1497     global $CFG;
1498     $names = get_list_of_plugins($dir);
1499     $ret_names = array();
1501     foreach ($names as $name) {
1502         require_once($CFG->dirroot.'/'.$dir.'/'.$name.'/lib.php');
1503         if (class_exists('feedback_item_'.$name)) {
1504             $ret_names[] = $name;
1505         }
1506     }
1507     return $ret_names;
1510 /**
1511  * load the available item plugins to use as dropdown-options
1512  *
1513  * @global object
1514  * @return array pluginnames as string
1515  */
1516 function feedback_load_feedback_items_options() {
1517     global $CFG;
1519     $feedback_options = array("pagebreak" => get_string('add_pagebreak', 'feedback'));
1521     if (!$feedback_names = feedback_load_feedback_items('mod/feedback/item')) {
1522         return array();
1523     }
1525     foreach ($feedback_names as $fn) {
1526         $feedback_options[$fn] = get_string($fn, 'feedback');
1527     }
1528     asort($feedback_options);
1529     return $feedback_options;
1532 /**
1533  * load the available items for the depend item dropdown list shown in the edit_item form
1534  *
1535  * @global object
1536  * @param object $feedback
1537  * @param object $item the item of the edit_item form
1538  * @return array all items except the item $item, labels and pagebreaks
1539  */
1540 function feedback_get_depend_candidates_for_item($feedback, $item) {
1541     global $DB;
1542     //all items for dependitem
1543     $where = "feedback = ? AND typ != 'pagebreak' AND hasvalue = 1";
1544     $params = array($feedback->id);
1545     if (isset($item->id) AND $item->id) {
1546         $where .= ' AND id != ?';
1547         $params[] = $item->id;
1548     }
1549     $dependitems = array(0 => get_string('choose'));
1550     $feedbackitems = $DB->get_records_select_menu('feedback_item',
1551                                                   $where,
1552                                                   $params,
1553                                                   'position',
1554                                                   'id, label');
1556     if (!$feedbackitems) {
1557         return $dependitems;
1558     }
1559     //adding the choose-option
1560     foreach ($feedbackitems as $key => $val) {
1561         if (trim(strval($val)) !== '') {
1562             $dependitems[$key] = format_string($val);
1563         }
1564     }
1565     return $dependitems;
1568 /**
1569  * @deprecated since 3.1
1570  */
1571 function feedback_create_item() {
1572     throw new coding_exception('feedback_create_item() can not be used anymore.');
1575 /**
1576  * save the changes of a given item.
1577  *
1578  * @global object
1579  * @param object $item
1580  * @return boolean
1581  */
1582 function feedback_update_item($item) {
1583     global $DB;
1584     return $DB->update_record("feedback_item", $item);
1587 /**
1588  * deletes an item and also deletes all related values
1589  *
1590  * @global object
1591  * @uses CONTEXT_MODULE
1592  * @param int $itemid
1593  * @param boolean $renumber should the kept items renumbered Yes/No
1594  * @param object $template if the template is given so the items are bound to it
1595  * @return void
1596  */
1597 function feedback_delete_item($itemid, $renumber = true, $template = false) {
1598     global $DB;
1600     $item = $DB->get_record('feedback_item', array('id'=>$itemid));
1602     //deleting the files from the item
1603     $fs = get_file_storage();
1605     if ($template) {
1606         if ($template->ispublic) {
1607             $context = context_system::instance();
1608         } else {
1609             $context = context_course::instance($template->course);
1610         }
1611         $templatefiles = $fs->get_area_files($context->id,
1612                                     'mod_feedback',
1613                                     'template',
1614                                     $item->id,
1615                                     "id",
1616                                     false);
1618         if ($templatefiles) {
1619             $fs->delete_area_files($context->id, 'mod_feedback', 'template', $item->id);
1620         }
1621     } else {
1622         if (!$cm = get_coursemodule_from_instance('feedback', $item->feedback)) {
1623             return false;
1624         }
1625         $context = context_module::instance($cm->id);
1627         $itemfiles = $fs->get_area_files($context->id,
1628                                     'mod_feedback',
1629                                     'item',
1630                                     $item->id,
1631                                     "id", false);
1633         if ($itemfiles) {
1634             $fs->delete_area_files($context->id, 'mod_feedback', 'item', $item->id);
1635         }
1636     }
1638     $DB->delete_records("feedback_value", array("item"=>$itemid));
1639     $DB->delete_records("feedback_valuetmp", array("item"=>$itemid));
1641     //remove all depends
1642     $DB->set_field('feedback_item', 'dependvalue', '', array('dependitem'=>$itemid));
1643     $DB->set_field('feedback_item', 'dependitem', 0, array('dependitem'=>$itemid));
1645     $DB->delete_records("feedback_item", array("id"=>$itemid));
1646     if ($renumber) {
1647         feedback_renumber_items($item->feedback);
1648     }
1651 /**
1652  * deletes all items of the given feedbackid
1653  *
1654  * @global object
1655  * @param int $feedbackid
1656  * @return void
1657  */
1658 function feedback_delete_all_items($feedbackid) {
1659     global $DB, $CFG;
1660     require_once($CFG->libdir.'/completionlib.php');
1662     if (!$feedback = $DB->get_record('feedback', array('id'=>$feedbackid))) {
1663         return false;
1664     }
1666     if (!$cm = get_coursemodule_from_instance('feedback', $feedback->id)) {
1667         return false;
1668     }
1670     if (!$course = $DB->get_record('course', array('id'=>$feedback->course))) {
1671         return false;
1672     }
1674     if (!$items = $DB->get_records('feedback_item', array('feedback'=>$feedbackid))) {
1675         return;
1676     }
1677     foreach ($items as $item) {
1678         feedback_delete_item($item->id, false);
1679     }
1680     if ($completeds = $DB->get_records('feedback_completed', array('feedback'=>$feedback->id))) {
1681         $completion = new completion_info($course);
1682         foreach ($completeds as $completed) {
1683             $DB->delete_records('feedback_completed', array('id' => $completed->id));
1684             // Update completion state
1685             if ($completion->is_enabled($cm) && $cm->completion == COMPLETION_TRACKING_AUTOMATIC &&
1686                     $feedback->completionsubmit) {
1687                 $completion->update_state($cm, COMPLETION_INCOMPLETE, $completed->userid);
1688             }
1689         }
1690     }
1692     $DB->delete_records('feedback_completedtmp', array('feedback'=>$feedbackid));
1696 /**
1697  * this function toggled the item-attribute required (yes/no)
1698  *
1699  * @global object
1700  * @param object $item
1701  * @return boolean
1702  */
1703 function feedback_switch_item_required($item) {
1704     global $DB, $CFG;
1706     $itemobj = feedback_get_item_class($item->typ);
1708     if ($itemobj->can_switch_require()) {
1709         $new_require_val = (int)!(bool)$item->required;
1710         $params = array('id'=>$item->id);
1711         $DB->set_field('feedback_item', 'required', $new_require_val, $params);
1712     }
1713     return true;
1716 /**
1717  * renumbers all items of the given feedbackid
1718  *
1719  * @global object
1720  * @param int $feedbackid
1721  * @return void
1722  */
1723 function feedback_renumber_items($feedbackid) {
1724     global $DB;
1726     $items = $DB->get_records('feedback_item', array('feedback'=>$feedbackid), 'position');
1727     $pos = 1;
1728     if ($items) {
1729         foreach ($items as $item) {
1730             $DB->set_field('feedback_item', 'position', $pos, array('id'=>$item->id));
1731             $pos++;
1732         }
1733     }
1736 /**
1737  * this decreases the position of the given item
1738  *
1739  * @global object
1740  * @param object $item
1741  * @return bool
1742  */
1743 function feedback_moveup_item($item) {
1744     global $DB;
1746     if ($item->position == 1) {
1747         return true;
1748     }
1750     $params = array('feedback'=>$item->feedback);
1751     if (!$items = $DB->get_records('feedback_item', $params, 'position')) {
1752         return false;
1753     }
1755     $itembefore = null;
1756     foreach ($items as $i) {
1757         if ($i->id == $item->id) {
1758             if (is_null($itembefore)) {
1759                 return true;
1760             }
1761             $itembefore->position = $item->position;
1762             $item->position--;
1763             feedback_update_item($itembefore);
1764             feedback_update_item($item);
1765             feedback_renumber_items($item->feedback);
1766             return true;
1767         }
1768         $itembefore = $i;
1769     }
1770     return false;
1773 /**
1774  * this increased the position of the given item
1775  *
1776  * @global object
1777  * @param object $item
1778  * @return bool
1779  */
1780 function feedback_movedown_item($item) {
1781     global $DB;
1783     $params = array('feedback'=>$item->feedback);
1784     if (!$items = $DB->get_records('feedback_item', $params, 'position')) {
1785         return false;
1786     }
1788     $movedownitem = null;
1789     foreach ($items as $i) {
1790         if (!is_null($movedownitem) AND $movedownitem->id == $item->id) {
1791             $movedownitem->position = $i->position;
1792             $i->position--;
1793             feedback_update_item($movedownitem);
1794             feedback_update_item($i);
1795             feedback_renumber_items($item->feedback);
1796             return true;
1797         }
1798         $movedownitem = $i;
1799     }
1800     return false;
1803 /**
1804  * here the position of the given item will be set to the value in $pos
1805  *
1806  * @global object
1807  * @param object $moveitem
1808  * @param int $pos
1809  * @return boolean
1810  */
1811 function feedback_move_item($moveitem, $pos) {
1812     global $DB;
1814     $params = array('feedback'=>$moveitem->feedback);
1815     if (!$allitems = $DB->get_records('feedback_item', $params, 'position')) {
1816         return false;
1817     }
1818     if (is_array($allitems)) {
1819         $index = 1;
1820         foreach ($allitems as $item) {
1821             if ($index == $pos) {
1822                 $index++;
1823             }
1824             if ($item->id == $moveitem->id) {
1825                 $moveitem->position = $pos;
1826                 feedback_update_item($moveitem);
1827                 continue;
1828             }
1829             $item->position = $index;
1830             feedback_update_item($item);
1831             $index++;
1832         }
1833         return true;
1834     }
1835     return false;
1838 /**
1839  * @deprecated since Moodle 3.1
1840  */
1841 function feedback_print_item_preview() {
1842     throw new coding_exception('feedback_print_item_preview() can not be used anymore. '
1843             . 'Items must implement complete_form_element().');
1846 /**
1847  * @deprecated since Moodle 3.1
1848  */
1849 function feedback_print_item_complete() {
1850     throw new coding_exception('feedback_print_item_complete() can not be used anymore. '
1851         . 'Items must implement complete_form_element().');
1854 /**
1855  * @deprecated since Moodle 3.1
1856  */
1857 function feedback_print_item_show_value() {
1858     throw new coding_exception('feedback_print_item_show_value() can not be used anymore. '
1859         . 'Items must implement complete_form_element().');
1862 /**
1863  * if the user completes a feedback and there is a pagebreak so the values are saved temporary.
1864  * the values are not saved permanently until the user click on save button
1865  *
1866  * @global object
1867  * @param object $feedbackcompleted
1868  * @return object temporary saved completed-record
1869  */
1870 function feedback_set_tmp_values($feedbackcompleted) {
1871     global $DB;
1873     //first we create a completedtmp
1874     $tmpcpl = new stdClass();
1875     foreach ($feedbackcompleted as $key => $value) {
1876         $tmpcpl->{$key} = $value;
1877     }
1878     unset($tmpcpl->id);
1879     $tmpcpl->timemodified = time();
1880     $tmpcpl->id = $DB->insert_record('feedback_completedtmp', $tmpcpl);
1881     //get all values of original-completed
1882     if (!$values = $DB->get_records('feedback_value', array('completed'=>$feedbackcompleted->id))) {
1883         return;
1884     }
1885     foreach ($values as $value) {
1886         unset($value->id);
1887         $value->completed = $tmpcpl->id;
1888         $DB->insert_record('feedback_valuetmp', $value);
1889     }
1890     return $tmpcpl;
1893 /**
1894  * this saves the temporary saved values permanently
1895  *
1896  * @global object
1897  * @param object $feedbackcompletedtmp the temporary completed
1898  * @param object $feedbackcompleted the target completed
1899  * @return int the id of the completed
1900  */
1901 function feedback_save_tmp_values($feedbackcompletedtmp, $feedbackcompleted) {
1902     global $DB;
1904     $tmpcplid = $feedbackcompletedtmp->id;
1905     if ($feedbackcompleted) {
1906         //first drop all existing values
1907         $DB->delete_records('feedback_value', array('completed'=>$feedbackcompleted->id));
1908         //update the current completed
1909         $feedbackcompleted->timemodified = time();
1910         $DB->update_record('feedback_completed', $feedbackcompleted);
1911     } else {
1912         $feedbackcompleted = clone($feedbackcompletedtmp);
1913         $feedbackcompleted->id = '';
1914         $feedbackcompleted->timemodified = time();
1915         $feedbackcompleted->id = $DB->insert_record('feedback_completed', $feedbackcompleted);
1916     }
1918     $allitems = $DB->get_records('feedback_item', array('feedback' => $feedbackcompleted->feedback));
1920     //save all the new values from feedback_valuetmp
1921     //get all values of tmp-completed
1922     $params = array('completed'=>$feedbackcompletedtmp->id);
1923     $values = $DB->get_records('feedback_valuetmp', $params);
1924     foreach ($values as $value) {
1925         //check if there are depend items
1926         $item = $DB->get_record('feedback_item', array('id'=>$value->item));
1927         if ($item->dependitem > 0 && isset($allitems[$item->dependitem])) {
1928             $check = feedback_compare_item_value($tmpcplid,
1929                                         $allitems[$item->dependitem],
1930                                         $item->dependvalue,
1931                                         true);
1932         } else {
1933             $check = true;
1934         }
1935         if ($check) {
1936             unset($value->id);
1937             $value->completed = $feedbackcompleted->id;
1938             $DB->insert_record('feedback_value', $value);
1939         }
1940     }
1941     //drop all the tmpvalues
1942     $DB->delete_records('feedback_valuetmp', array('completed'=>$tmpcplid));
1943     $DB->delete_records('feedback_completedtmp', array('id'=>$tmpcplid));
1945     // Trigger event for the delete action we performed.
1946     $cm = get_coursemodule_from_instance('feedback', $feedbackcompleted->feedback);
1947     $event = \mod_feedback\event\response_submitted::create_from_record($feedbackcompleted, $cm);
1948     $event->trigger();
1949     return $feedbackcompleted->id;
1953 /**
1954  * @deprecated since Moodle 3.1
1955  */
1956 function feedback_delete_completedtmp() {
1957     throw new coding_exception('feedback_delete_completedtmp() can not be used anymore.');
1961 ////////////////////////////////////////////////
1962 ////////////////////////////////////////////////
1963 ////////////////////////////////////////////////
1964 //functions to handle the pagebreaks
1965 ////////////////////////////////////////////////
1967 /**
1968  * this creates a pagebreak.
1969  * a pagebreak is a special kind of item
1970  *
1971  * @global object
1972  * @param int $feedbackid
1973  * @return mixed false if there already is a pagebreak on last position or the id of the pagebreak-item
1974  */
1975 function feedback_create_pagebreak($feedbackid) {
1976     global $DB;
1978     //check if there already is a pagebreak on the last position
1979     $lastposition = $DB->count_records('feedback_item', array('feedback'=>$feedbackid));
1980     if ($lastposition == feedback_get_last_break_position($feedbackid)) {
1981         return false;
1982     }
1984     $item = new stdClass();
1985     $item->feedback = $feedbackid;
1987     $item->template=0;
1989     $item->name = '';
1991     $item->presentation = '';
1992     $item->hasvalue = 0;
1994     $item->typ = 'pagebreak';
1995     $item->position = $lastposition + 1;
1997     $item->required=0;
1999     return $DB->insert_record('feedback_item', $item);
2002 /**
2003  * get all positions of pagebreaks in the given feedback
2004  *
2005  * @global object
2006  * @param int $feedbackid
2007  * @return array all ordered pagebreak positions
2008  */
2009 function feedback_get_all_break_positions($feedbackid) {
2010     global $DB;
2012     $params = array('typ'=>'pagebreak', 'feedback'=>$feedbackid);
2013     $allbreaks = $DB->get_records_menu('feedback_item', $params, 'position', 'id, position');
2014     if (!$allbreaks) {
2015         return false;
2016     }
2017     return array_values($allbreaks);
2020 /**
2021  * get the position of the last pagebreak
2022  *
2023  * @param int $feedbackid
2024  * @return int the position of the last pagebreak
2025  */
2026 function feedback_get_last_break_position($feedbackid) {
2027     if (!$allbreaks = feedback_get_all_break_positions($feedbackid)) {
2028         return false;
2029     }
2030     return $allbreaks[count($allbreaks) - 1];
2033 /**
2034  * @deprecated since Moodle 3.1
2035  */
2036 function feedback_get_page_to_continue() {
2037     throw new coding_exception('feedback_get_page_to_continue() can not be used anymore.');
2040 ////////////////////////////////////////////////
2041 ////////////////////////////////////////////////
2042 ////////////////////////////////////////////////
2043 //functions to handle the values
2044 ////////////////////////////////////////////////
2046 /**
2047  * @deprecated since Moodle 3.1
2048  */
2049 function feedback_clean_input_value() {
2050     throw new coding_exception('feedback_clean_input_value() can not be used anymore. '
2051         . 'Items must implement complete_form_element().');
2055 /**
2056  * @deprecated since Moodle 3.1
2057  */
2058 function feedback_save_values() {
2059     throw new coding_exception('feedback_save_values() can not be used anymore.');
2062 /**
2063  * @deprecated since Moodle 3.1
2064  */
2065 function feedback_save_guest_values() {
2066     throw new coding_exception('feedback_save_guest_values() can not be used anymore.');
2069 /**
2070  * get the value from the given item related to the given completed.
2071  * the value can come as temporary or as permanently value. the deciding is done by $tmp
2072  *
2073  * @global object
2074  * @param int $completeid
2075  * @param int $itemid
2076  * @param boolean $tmp
2077  * @return mixed the value, the type depends on plugin-definition
2078  */
2079 function feedback_get_item_value($completedid, $itemid, $tmp = false) {
2080     global $DB;
2082     $tmpstr = $tmp ? 'tmp' : '';
2083     $params = array('completed'=>$completedid, 'item'=>$itemid);
2084     return $DB->get_field('feedback_value'.$tmpstr, 'value', $params);
2087 /**
2088  * compares the value of the itemid related to the completedid with the dependvalue.
2089  * this is used if a depend item is set.
2090  * the value can come as temporary or as permanently value. the deciding is done by $tmp.
2091  *
2092  * @param int $completedid
2093  * @param stdClass|int $item
2094  * @param mixed $dependvalue
2095  * @param bool $tmp
2096  * @return bool
2097  */
2098 function feedback_compare_item_value($completedid, $item, $dependvalue, $tmp = false) {
2099     global $DB;
2101     if (is_int($item)) {
2102         $item = $DB->get_record('feedback_item', array('id' => $item));
2103     }
2105     $dbvalue = feedback_get_item_value($completedid, $item->id, $tmp);
2107     $itemobj = feedback_get_item_class($item->typ);
2108     return $itemobj->compare_value($item, $dbvalue, $dependvalue); //true or false
2111 /**
2112  * @deprecated since Moodle 3.1
2113  */
2114 function feedback_check_values() {
2115     throw new coding_exception('feedback_check_values() can not be used anymore. '
2116         . 'Items must implement complete_form_element().');
2119 /**
2120  * @deprecated since Moodle 3.1
2121  */
2122 function feedback_create_values() {
2123     throw new coding_exception('feedback_create_values() can not be used anymore.');
2126 /**
2127  * @deprecated since Moodle 3.1
2128  */
2129 function feedback_update_values() {
2130     throw new coding_exception('feedback_update_values() can not be used anymore.');
2133 /**
2134  * get the values of an item depending on the given groupid.
2135  * if the feedback is anonymous so the values are shuffled
2136  *
2137  * @global object
2138  * @global object
2139  * @param object $item
2140  * @param int $groupid
2141  * @param int $courseid
2142  * @param bool $ignore_empty if this is set true so empty values are not delivered
2143  * @return array the value-records
2144  */
2145 function feedback_get_group_values($item,
2146                                    $groupid = false,
2147                                    $courseid = false,
2148                                    $ignore_empty = false) {
2150     global $CFG, $DB;
2152     //if the groupid is given?
2153     if (intval($groupid) > 0) {
2154         $params = array();
2155         if ($ignore_empty) {
2156             $value = $DB->sql_compare_text('fbv.value');
2157             $ignore_empty_select = "AND $value != :emptyvalue AND $value != :zerovalue";
2158             $params += array('emptyvalue' => '', 'zerovalue' => '0');
2159         } else {
2160             $ignore_empty_select = "";
2161         }
2163         $query = 'SELECT fbv .  *
2164                     FROM {feedback_value} fbv, {feedback_completed} fbc, {groups_members} gm
2165                    WHERE fbv.item = :itemid
2166                          AND fbv.completed = fbc.id
2167                          AND fbc.userid = gm.userid
2168                          '.$ignore_empty_select.'
2169                          AND gm.groupid = :groupid
2170                 ORDER BY fbc.timemodified';
2171         $params += array('itemid' => $item->id, 'groupid' => $groupid);
2172         $values = $DB->get_records_sql($query, $params);
2174     } else {
2175         $params = array();
2176         if ($ignore_empty) {
2177             $value = $DB->sql_compare_text('value');
2178             $ignore_empty_select = "AND $value != :emptyvalue AND $value != :zerovalue";
2179             $params += array('emptyvalue' => '', 'zerovalue' => '0');
2180         } else {
2181             $ignore_empty_select = "";
2182         }
2184         if ($courseid) {
2185             $select = "item = :itemid AND course_id = :courseid ".$ignore_empty_select;
2186             $params += array('itemid' => $item->id, 'courseid' => $courseid);
2187             $values = $DB->get_records_select('feedback_value', $select, $params);
2188         } else {
2189             $select = "item = :itemid ".$ignore_empty_select;
2190             $params += array('itemid' => $item->id);
2191             $values = $DB->get_records_select('feedback_value', $select, $params);
2192         }
2193     }
2194     $params = array('id'=>$item->feedback);
2195     if ($DB->get_field('feedback', 'anonymous', $params) == FEEDBACK_ANONYMOUS_YES) {
2196         if (is_array($values)) {
2197             shuffle($values);
2198         }
2199     }
2200     return $values;
2203 /**
2204  * check for multiple_submit = false.
2205  * if the feedback is global so the courseid must be given
2206  *
2207  * @global object
2208  * @global object
2209  * @param int $feedbackid
2210  * @param int $courseid
2211  * @return boolean true if the feedback already is submitted otherwise false
2212  */
2213 function feedback_is_already_submitted($feedbackid, $courseid = false) {
2214     global $USER, $DB;
2216     if (!isloggedin() || isguestuser()) {
2217         return false;
2218     }
2220     $params = array('userid' => $USER->id, 'feedback' => $feedbackid);
2221     if ($courseid) {
2222         $params['courseid'] = $courseid;
2223     }
2224     return $DB->record_exists('feedback_completed', $params);
2227 /**
2228  * @deprecated since Moodle 3.1. Use feedback_get_current_completed_tmp() or feedback_get_last_completed.
2229  */
2230 function feedback_get_current_completed() {
2231     throw new coding_exception('feedback_get_current_completed() can not be used anymore. Please ' .
2232             'use either feedback_get_current_completed_tmp() or feedback_get_last_completed()');
2235 /**
2236  * get the completeds depending on the given groupid.
2237  *
2238  * @global object
2239  * @global object
2240  * @param object $feedback
2241  * @param int $groupid
2242  * @param int $courseid
2243  * @return mixed array of found completeds otherwise false
2244  */
2245 function feedback_get_completeds_group($feedback, $groupid = false, $courseid = false) {
2246     global $CFG, $DB;
2248     if (intval($groupid) > 0) {
2249         $query = "SELECT fbc.*
2250                     FROM {feedback_completed} fbc, {groups_members} gm
2251                    WHERE fbc.feedback = ?
2252                          AND gm.groupid = ?
2253                          AND fbc.userid = gm.userid";
2254         if ($values = $DB->get_records_sql($query, array($feedback->id, $groupid))) {
2255             return $values;
2256         } else {
2257             return false;
2258         }
2259     } else {
2260         if ($courseid) {
2261             $query = "SELECT DISTINCT fbc.*
2262                         FROM {feedback_completed} fbc, {feedback_value} fbv
2263                         WHERE fbc.id = fbv.completed
2264                             AND fbc.feedback = ?
2265                             AND fbv.course_id = ?
2266                         ORDER BY random_response";
2267             if ($values = $DB->get_records_sql($query, array($feedback->id, $courseid))) {
2268                 return $values;
2269             } else {
2270                 return false;
2271             }
2272         } else {
2273             if ($values = $DB->get_records('feedback_completed', array('feedback'=>$feedback->id))) {
2274                 return $values;
2275             } else {
2276                 return false;
2277             }
2278         }
2279     }
2282 /**
2283  * get the count of completeds depending on the given groupid.
2284  *
2285  * @global object
2286  * @global object
2287  * @param object $feedback
2288  * @param int $groupid
2289  * @param int $courseid
2290  * @return mixed count of completeds or false
2291  */
2292 function feedback_get_completeds_group_count($feedback, $groupid = false, $courseid = false) {
2293     global $CFG, $DB;
2295     if ($courseid > 0 AND !$groupid <= 0) {
2296         $sql = "SELECT id, COUNT(item) AS ci
2297                   FROM {feedback_value}
2298                  WHERE course_id  = ?
2299               GROUP BY item ORDER BY ci DESC";
2300         if ($foundrecs = $DB->get_records_sql($sql, array($courseid))) {
2301             $foundrecs = array_values($foundrecs);
2302             return $foundrecs[0]->ci;
2303         }
2304         return false;
2305     }
2306     if ($values = feedback_get_completeds_group($feedback, $groupid)) {
2307         return count($values);
2308     } else {
2309         return false;
2310     }
2313 /**
2314  * deletes all completed-recordsets from a feedback.
2315  * all related data such as values also will be deleted
2316  *
2317  * @param stdClass|int $feedback
2318  * @param stdClass|cm_info $cm
2319  * @param stdClass $course
2320  * @return void
2321  */
2322 function feedback_delete_all_completeds($feedback, $cm = null, $course = null) {
2323     global $DB;
2325     if (is_int($feedback)) {
2326         $feedback = $DB->get_record('feedback', array('id' => $feedback));
2327     }
2329     if (!$completeds = $DB->get_records('feedback_completed', array('feedback' => $feedback->id))) {
2330         return;
2331     }
2333     if (!$course && !($course = $DB->get_record('course', array('id' => $feedback->course)))) {
2334         return false;
2335     }
2337     if (!$cm && !($cm = get_coursemodule_from_instance('feedback', $feedback->id))) {
2338         return false;
2339     }
2341     foreach ($completeds as $completed) {
2342         feedback_delete_completed($completed, $feedback, $cm, $course);
2343     }
2346 /**
2347  * deletes a completed given by completedid.
2348  * all related data such values or tracking data also will be deleted
2349  *
2350  * @param int|stdClass $completed
2351  * @param stdClass $feedback
2352  * @param stdClass|cm_info $cm
2353  * @param stdClass $course
2354  * @return boolean
2355  */
2356 function feedback_delete_completed($completed, $feedback = null, $cm = null, $course = null) {
2357     global $DB, $CFG;
2358     require_once($CFG->libdir.'/completionlib.php');
2360     if (!isset($completed->id)) {
2361         if (!$completed = $DB->get_record('feedback_completed', array('id' => $completed))) {
2362             return false;
2363         }
2364     }
2366     if (!$feedback && !($feedback = $DB->get_record('feedback', array('id' => $completed->feedback)))) {
2367         return false;
2368     }
2370     if (!$course && !($course = $DB->get_record('course', array('id' => $feedback->course)))) {
2371         return false;
2372     }
2374     if (!$cm && !($cm = get_coursemodule_from_instance('feedback', $feedback->id))) {
2375         return false;
2376     }
2378     //first we delete all related values
2379     $DB->delete_records('feedback_value', array('completed' => $completed->id));
2381     // Delete the completed record.
2382     $return = $DB->delete_records('feedback_completed', array('id' => $completed->id));
2384     // Update completion state
2385     $completion = new completion_info($course);
2386     if ($completion->is_enabled($cm) && $cm->completion == COMPLETION_TRACKING_AUTOMATIC && $feedback->completionsubmit) {
2387         $completion->update_state($cm, COMPLETION_INCOMPLETE, $completed->userid);
2388     }
2389     // Trigger event for the delete action we performed.
2390     $event = \mod_feedback\event\response_deleted::create_from_record($completed, $cm, $feedback);
2391     $event->trigger();
2393     return $return;
2396 ////////////////////////////////////////////////
2397 ////////////////////////////////////////////////
2398 ////////////////////////////////////////////////
2399 //functions to handle sitecourse mapping
2400 ////////////////////////////////////////////////
2402 /**
2403  * @deprecated since 3.1
2404  */
2405 function feedback_is_course_in_sitecourse_map() {
2406     throw new coding_exception('feedback_is_course_in_sitecourse_map() can not be used anymore.');
2409 /**
2410  * @deprecated since 3.1
2411  */
2412 function feedback_is_feedback_in_sitecourse_map() {
2413     throw new coding_exception('feedback_is_feedback_in_sitecourse_map() can not be used anymore.');
2416 /**
2417  * gets the feedbacks from table feedback_sitecourse_map.
2418  * this is used to show the global feedbacks on the feedback block
2419  * all feedbacks with the following criteria will be selected:<br />
2420  *
2421  * 1) all feedbacks which id are listed together with the courseid in sitecoursemap and<br />
2422  * 2) all feedbacks which not are listed in sitecoursemap
2423  *
2424  * @global object
2425  * @param int $courseid
2426  * @return array the feedback-records
2427  */
2428 function feedback_get_feedbacks_from_sitecourse_map($courseid) {
2429     global $DB;
2431     //first get all feedbacks listed in sitecourse_map with named courseid
2432     $sql = "SELECT f.id AS id,
2433                    cm.id AS cmid,
2434                    f.name AS name,
2435                    f.timeopen AS timeopen,
2436                    f.timeclose AS timeclose
2437             FROM {feedback} f, {course_modules} cm, {feedback_sitecourse_map} sm, {modules} m
2438             WHERE f.id = cm.instance
2439                    AND f.course = '".SITEID."'
2440                    AND m.id = cm.module
2441                    AND m.name = 'feedback'
2442                    AND sm.courseid = ?
2443                    AND sm.feedbackid = f.id";
2445     if (!$feedbacks1 = $DB->get_records_sql($sql, array($courseid))) {
2446         $feedbacks1 = array();
2447     }
2449     //second get all feedbacks not listed in sitecourse_map
2450     $feedbacks2 = array();
2451     $sql = "SELECT f.id AS id,
2452                    cm.id AS cmid,
2453                    f.name AS name,
2454                    f.timeopen AS timeopen,
2455                    f.timeclose AS timeclose
2456             FROM {feedback} f, {course_modules} cm, {modules} m
2457             WHERE f.id = cm.instance
2458                    AND f.course = '".SITEID."'
2459                    AND m.id = cm.module
2460                    AND m.name = 'feedback'";
2461     if (!$allfeedbacks = $DB->get_records_sql($sql)) {
2462         $allfeedbacks = array();
2463     }
2464     foreach ($allfeedbacks as $a) {
2465         if (!$DB->record_exists('feedback_sitecourse_map', array('feedbackid'=>$a->id))) {
2466             $feedbacks2[] = $a;
2467         }
2468     }
2470     $feedbacks = array_merge($feedbacks1, $feedbacks2);
2471     $modinfo = get_fast_modinfo(SITEID);
2472     return array_filter($feedbacks, function($f) use ($modinfo) {
2473         return ($cm = $modinfo->get_cm($f->cmid)) && $cm->uservisible;
2474     });
2478 /**
2479  * Gets the courses from table feedback_sitecourse_map
2480  *
2481  * @param int $feedbackid
2482  * @return array the course-records
2483  */
2484 function feedback_get_courses_from_sitecourse_map($feedbackid) {
2485     global $DB;
2487     $sql = "SELECT c.id, c.fullname, c.shortname
2488               FROM {feedback_sitecourse_map} f, {course} c
2489              WHERE c.id = f.courseid
2490                    AND f.feedbackid = ?
2491           ORDER BY c.fullname";
2493     return $DB->get_records_sql($sql, array($feedbackid));
2497 /**
2498  * Updates the course mapping for the feedback
2499  *
2500  * @param stdClass $feedback
2501  * @param array $courses array of course ids
2502  */
2503 function feedback_update_sitecourse_map($feedback, $courses) {
2504     global $DB;
2505     if (empty($courses)) {
2506         $courses = array();
2507     }
2508     $currentmapping = $DB->get_fieldset_select('feedback_sitecourse_map', 'courseid', 'feedbackid=?', array($feedback->id));
2509     foreach (array_diff($courses, $currentmapping) as $courseid) {
2510         $DB->insert_record('feedback_sitecourse_map', array('feedbackid' => $feedback->id, 'courseid' => $courseid));
2511     }
2512     foreach (array_diff($currentmapping, $courses) as $courseid) {
2513         $DB->delete_records('feedback_sitecourse_map', array('feedbackid' => $feedback->id, 'courseid' => $courseid));
2514     }
2515     // TODO MDL-53574 add events.
2518 /**
2519  * @deprecated since 3.1
2520  */
2521 function feedback_clean_up_sitecourse_map() {
2522     throw new coding_exception('feedback_clean_up_sitecourse_map() can not be used anymore.');
2525 ////////////////////////////////////////////////
2526 ////////////////////////////////////////////////
2527 ////////////////////////////////////////////////
2528 //not relatable functions
2529 ////////////////////////////////////////////////
2531 /**
2532  * @deprecated since 3.1
2533  */
2534 function feedback_print_numeric_option_list() {
2535     throw new coding_exception('feedback_print_numeric_option_list() can not be used anymore.');
2538 /**
2539  * sends an email to the teachers of the course where the given feedback is placed.
2540  *
2541  * @global object
2542  * @global object
2543  * @uses FEEDBACK_ANONYMOUS_NO
2544  * @uses FORMAT_PLAIN
2545  * @param object $cm the coursemodule-record
2546  * @param object $feedback
2547  * @param object $course
2548  * @param stdClass|int $user
2549  * @param stdClass $completed record from feedback_completed if known
2550  * @return void
2551  */
2552 function feedback_send_email($cm, $feedback, $course, $user, $completed = null) {
2553     global $CFG, $DB, $PAGE;
2555     if ($feedback->email_notification == 0) {  // No need to do anything
2556         return;
2557     }
2559     if (is_int($user)) {
2560         $user = $DB->get_record('user', array('id' => $user));
2561     }
2563     if (isset($cm->groupmode) && empty($course->groupmodeforce)) {
2564         $groupmode =  $cm->groupmode;
2565     } else {
2566         $groupmode = $course->groupmode;
2567     }
2569     if ($groupmode == SEPARATEGROUPS) {
2570         $groups = $DB->get_records_sql_menu("SELECT g.name, g.id
2571                                                FROM {groups} g, {groups_members} m
2572                                               WHERE g.courseid = ?
2573                                                     AND g.id = m.groupid
2574                                                     AND m.userid = ?
2575                                            ORDER BY name ASC", array($course->id, $user->id));
2576         $groups = array_values($groups);
2578         $teachers = feedback_get_receivemail_users($cm->id, $groups);
2579     } else {
2580         $teachers = feedback_get_receivemail_users($cm->id);
2581     }
2583     if ($teachers) {
2585         $strfeedbacks = get_string('modulenameplural', 'feedback');
2586         $strfeedback  = get_string('modulename', 'feedback');
2588         if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO) {
2589             $printusername = fullname($user);
2590         } else {
2591             $printusername = get_string('anonymous_user', 'feedback');
2592         }
2594         foreach ($teachers as $teacher) {
2595             $info = new stdClass();
2596             $info->username = $printusername;
2597             $info->feedback = format_string($feedback->name, true);
2598             $info->url = $CFG->wwwroot.'/mod/feedback/show_entries.php?'.
2599                             'id='.$cm->id.'&'.
2600                             'userid=' . $user->id;
2601             if ($completed) {
2602                 $info->url .= '&showcompleted=' . $completed->id;
2603                 if ($feedback->course == SITEID) {
2604                     // Course where feedback was completed (for site feedbacks only).
2605                     $info->url .= '&courseid=' . $completed->courseid;
2606                 }
2607             }
2609             $a = array('username' => $info->username, 'feedbackname' => $feedback->name);
2611             $postsubject = get_string('feedbackcompleted', 'feedback', $a);
2612             $posttext = feedback_send_email_text($info, $course);
2614             if ($teacher->mailformat == 1) {
2615                 $posthtml = feedback_send_email_html($info, $course, $cm);
2616             } else {
2617                 $posthtml = '';
2618             }
2620             $customdata = [
2621                 'cmid' => $cm->id,
2622                 'instance' => $feedback->id,
2623             ];
2624             if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO) {
2625                 $eventdata = new \core\message\message();
2626                 $eventdata->courseid         = $course->id;
2627                 $eventdata->name             = 'submission';
2628                 $eventdata->component        = 'mod_feedback';
2629                 $eventdata->userfrom         = $user;
2630                 $eventdata->userto           = $teacher;
2631                 $eventdata->subject          = $postsubject;
2632                 $eventdata->fullmessage      = $posttext;
2633                 $eventdata->fullmessageformat = FORMAT_PLAIN;
2634                 $eventdata->fullmessagehtml  = $posthtml;
2635                 $eventdata->smallmessage     = '';
2636                 $eventdata->courseid         = $course->id;
2637                 $eventdata->contexturl       = $info->url;
2638                 $eventdata->contexturlname   = $info->feedback;
2639                 // User image.
2640                 $userpicture = new user_picture($user);
2641                 $userpicture->includetoken = $teacher->id; // Generate an out-of-session token for the user receiving the message.
2642                 $customdata['notificationiconurl'] = $userpicture->get_url($PAGE)->out(false);
2643                 $eventdata->customdata = $customdata;
2644                 message_send($eventdata);
2645             } else {
2646                 $eventdata = new \core\message\message();
2647                 $eventdata->courseid         = $course->id;
2648                 $eventdata->name             = 'submission';
2649                 $eventdata->component        = 'mod_feedback';
2650                 $eventdata->userfrom         = $teacher;
2651                 $eventdata->userto           = $teacher;
2652                 $eventdata->subject          = $postsubject;
2653                 $eventdata->fullmessage      = $posttext;
2654                 $eventdata->fullmessageformat = FORMAT_PLAIN;
2655                 $eventdata->fullmessagehtml  = $posthtml;
2656                 $eventdata->smallmessage     = '';
2657                 $eventdata->courseid         = $course->id;
2658                 $eventdata->contexturl       = $info->url;
2659                 $eventdata->contexturlname   = $info->feedback;
2660                 // Feedback icon if can be easily reachable.
2661                 $customdata['notificationiconurl'] = ($cm instanceof cm_info) ? $cm->get_icon_url()->out() : '';
2662                 $eventdata->customdata = $customdata;
2663                 message_send($eventdata);
2664             }
2665         }
2666     }
2669 /**
2670  * sends an email to the teachers of the course where the given feedback is placed.
2671  *
2672  * @global object
2673  * @uses FORMAT_PLAIN
2674  * @param object $cm the coursemodule-record
2675  * @param object $feedback
2676  * @param object $course
2677  * @return void
2678  */
2679 function feedback_send_email_anonym($cm, $feedback, $course) {
2680     global $CFG;
2682     if ($feedback->email_notification == 0) { // No need to do anything
2683         return;
2684     }
2686     $teachers = feedback_get_receivemail_users($cm->id);
2688     if ($teachers) {
2690         $strfeedbacks = get_string('modulenameplural', 'feedback');
2691         $strfeedback  = get_string('modulename', 'feedback');
2692         $printusername = get_string('anonymous_user', 'feedback');
2694         foreach ($teachers as $teacher) {
2695             $info = new stdClass();
2696             $info->username = $printusername;
2697             $info->feedback = format_string($feedback->name, true);
2698             $info->url = $CFG->wwwroot.'/mod/feedback/show_entries.php?id=' . $cm->id;
2700             $a = array('username' => $info->username, 'feedbackname' => $feedback->name);
2702             $postsubject = get_string('feedbackcompleted', 'feedback', $a);
2703             $posttext = feedback_send_email_text($info, $course);
2705             if ($teacher->mailformat == 1) {
2706                 $posthtml = feedback_send_email_html($info, $course, $cm);
2707             } else {
2708                 $posthtml = '';
2709             }
2711             $eventdata = new \core\message\message();
2712             $eventdata->courseid         = $course->id;
2713             $eventdata->name             = 'submission';
2714             $eventdata->component        = 'mod_feedback';
2715             $eventdata->userfrom         = $teacher;
2716             $eventdata->userto           = $teacher;
2717             $eventdata->subject          = $postsubject;
2718             $eventdata->fullmessage      = $posttext;
2719             $eventdata->fullmessageformat = FORMAT_PLAIN;
2720             $eventdata->fullmessagehtml  = $posthtml;
2721             $eventdata->smallmessage     = '';
2722             $eventdata->courseid         = $course->id;
2723             $eventdata->contexturl       = $info->url;
2724             $eventdata->contexturlname   = $info->feedback;
2725             $eventdata->customdata       = [
2726                 'cmid' => $cm->id,
2727                 'instance' => $feedback->id,
2728                 'notificationiconurl' => ($cm instanceof cm_info) ? $cm->get_icon_url()->out() : '',  // Performance wise.
2729             ];
2731             message_send($eventdata);
2732         }
2733     }
2736 /**
2737  * send the text-part of the email
2738  *
2739  * @param object $info includes some infos about the feedback you want to send
2740  * @param object $course
2741  * @return string the text you want to post
2742  */
2743 function feedback_send_email_text($info, $course) {
2744     $coursecontext = context_course::instance($course->id);
2745     $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
2746     $posttext  = $courseshortname.' -> '.get_string('modulenameplural', 'feedback').' -> '.
2747                     $info->feedback."\n";
2748     $posttext .= '---------------------------------------------------------------------'."\n";
2749     $posttext .= get_string("emailteachermail", "feedback", $info)."\n";
2750     $posttext .= '---------------------------------------------------------------------'."\n";
2751     return $posttext;
2755 /**
2756  * send the html-part of the email
2757  *
2758  * @global object
2759  * @param object $info includes some infos about the feedback you want to send
2760  * @param object $course
2761  * @return string the text you want to post
2762  */
2763 function feedback_send_email_html($info, $course, $cm) {
2764     global $CFG;
2765     $coursecontext = context_course::instance($course->id);
2766     $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
2767     $course_url = $CFG->wwwroot.'/course/view.php?id='.$course->id;
2768     $feedback_all_url = $CFG->wwwroot.'/mod/feedback/index.php?id='.$course->id;
2769     $feedback_url = $CFG->wwwroot.'/mod/feedback/view.php?id='.$cm->id;
2771     $posthtml = '<p><font face="sans-serif">'.
2772             '<a href="'.$course_url.'">'.$courseshortname.'</a> ->'.
2773             '<a href="'.$feedback_all_url.'">'.get_string('modulenameplural', 'feedback').'</a> ->'.
2774             '<a href="'.$feedback_url.'">'.$info->feedback.'</a></font></p>';
2775     $posthtml .= '<hr /><font face="sans-serif">';
2776     $posthtml .= '<p>'.get_string('emailteachermailhtml', 'feedback', $info).'</p>';
2777     $posthtml .= '</font><hr />';
2778     return $posthtml;
2781 /**
2782  * @param string $url
2783  * @return string
2784  */
2785 function feedback_encode_target_url($url) {
2786     if (strpos($url, '?')) {
2787         list($part1, $part2) = explode('?', $url, 2); //maximal 2 parts
2788         return $part1 . '?' . htmlentities($part2);
2789     } else {
2790         return $url;
2791     }
2794 /**
2795  * Adds module specific settings to the settings block
2796  *
2797  * @param settings_navigation $settings The settings navigation object
2798  * @param navigation_node $feedbacknode The node to add module settings to
2799  */
2800 function feedback_extend_settings_navigation(settings_navigation $settings,
2801                                              navigation_node $feedbacknode) {
2803     global $PAGE;
2805     if (!$context = context_module::instance($PAGE->cm->id, IGNORE_MISSING)) {
2806         print_error('badcontext');
2807     }
2809     if (has_capability('mod/feedback:edititems', $context)) {
2810         $questionnode = $feedbacknode->add(get_string('questions', 'feedback'));
2812         $questionnode->add(get_string('edit_items', 'feedback'),
2813                     new moodle_url('/mod/feedback/edit.php',
2814                                     array('id' => $PAGE->cm->id,
2815                                           'do_show' => 'edit')));
2817         $questionnode->add(get_string('export_questions', 'feedback'),
2818                     new moodle_url('/mod/feedback/export.php',
2819                                     array('id' => $PAGE->cm->id,
2820                                           'action' => 'exportfile')));
2822         $questionnode->add(get_string('import_questions', 'feedback'),
2823                     new moodle_url('/mod/feedback/import.php',
2824                                     array('id' => $PAGE->cm->id)));
2826         $questionnode->add(get_string('templates', 'feedback'),
2827                     new moodle_url('/mod/feedback/edit.php',
2828                                     array('id' => $PAGE->cm->id,
2829                                           'do_show' => 'templates')));
2830     }
2832     if (has_capability('mod/feedback:mapcourse', $context) && $PAGE->course->id == SITEID) {
2833         $feedbacknode->add(get_string('mappedcourses', 'feedback'),
2834                     new moodle_url('/mod/feedback/mapcourse.php',
2835                                     array('id' => $PAGE->cm->id)));
2836     }
2838     if (has_capability('mod/feedback:viewreports', $context)) {
2839         $feedback = $PAGE->activityrecord;
2840         if ($feedback->course == SITEID) {
2841             $feedbacknode->add(get_string('analysis', 'feedback'),
2842                     new moodle_url('/mod/feedback/analysis_course.php',
2843                                     array('id' => $PAGE->cm->id)));
2844         } else {
2845             $feedbacknode->add(get_string('analysis', 'feedback'),
2846                     new moodle_url('/mod/feedback/analysis.php',
2847                                     array('id' => $PAGE->cm->id)));
2848         }
2850         $feedbacknode->add(get_string('show_entries', 'feedback'),
2851                     new moodle_url('/mod/feedback/show_entries.php',
2852                                     array('id' => $PAGE->cm->id)));
2854         if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO AND $feedback->course != SITEID) {
2855             $feedbacknode->add(get_string('show_nonrespondents', 'feedback'),
2856                         new moodle_url('/mod/feedback/show_nonrespondents.php',
2857                                         array('id' => $PAGE->cm->id)));
2858         }
2859     }
2862 function feedback_init_feedback_session() {
2863     //initialize the feedback-Session - not nice at all!!
2864     global $SESSION;
2865     if (!empty($SESSION)) {
2866         if (!isset($SESSION->feedback) OR !is_object($SESSION->feedback)) {
2867             $SESSION->feedback = new stdClass();
2868         }
2869     }
2872 /**
2873  * Return a list of page types
2874  * @param string $pagetype current page type
2875  * @param stdClass $parentcontext Block's parent context
2876  * @param stdClass $currentcontext Current context of block
2877  */
2878 function feedback_page_type_list($pagetype, $parentcontext, $currentcontext) {
2879     $module_pagetype = array('mod-feedback-*'=>get_string('page-mod-feedback-x', 'feedback'));
2880     return $module_pagetype;
2883 /**
2884  * Move save the items of the given $feedback in the order of $itemlist.
2885  * @param string $itemlist a comma separated list with item ids
2886  * @param stdClass $feedback
2887  * @return bool true if success
2888  */
2889 function feedback_ajax_saveitemorder($itemlist, $feedback) {
2890     global $DB;
2892     $result = true;
2893     $position = 0;
2894     foreach ($itemlist as $itemid) {
2895         $position++;
2896         $result = $result && $DB->set_field('feedback_item',
2897                                             'position',
2898                                             $position,
2899                                             array('id'=>$itemid, 'feedback'=>$feedback->id));
2900     }
2901     return $result;
2904 /**
2905  * Checks if current user is able to view feedback on this course.
2906  *
2907  * @param stdClass $feedback
2908  * @param context_module $context
2909  * @param int $courseid
2910  * @return bool
2911  */
2912 function feedback_can_view_analysis($feedback, $context, $courseid = false) {
2913     if (has_capability('mod/feedback:viewreports', $context)) {
2914         return true;
2915     }
2917     if (intval($feedback->publish_stats) != 1 ||
2918             !has_capability('mod/feedback:viewanalysepage', $context)) {
2919         return false;
2920     }
2922     if (!isloggedin() || isguestuser()) {
2923         // There is no tracking for the guests, assume that they can view analysis if condition above is satisfied.
2924         return $feedback->course == SITEID;
2925     }
2927     return feedback_is_already_submitted($feedback->id, $courseid);
2930 /**
2931  * Get icon mapping for font-awesome.
2932  */
2933 function mod_feedback_get_fontawesome_icon_map() {
2934     return [
2935         'mod_feedback:required' => 'fa-exclamation-circle',
2936         'mod_feedback:notrequired' => 'fa-question-circle-o',
2937     ];
2940 /**
2941  * Check if the module has any update that affects the current user since a given time.
2942  *
2943  * @param  cm_info $cm course module data
2944  * @param  int $from the time to check updates from
2945  * @param  array $filter if we need to check only specific updates
2946  * @return stdClass an object with the different type of areas indicating if they were updated or not
2947  * @since Moodle 3.3
2948  */
2949 function feedback_check_updates_since(cm_info $cm, $from, $filter = array()) {
2950     global $DB, $USER, $CFG;
2952     $updates = course_check_module_updates_since($cm, $from, array(), $filter);
2954     // Check for new attempts.
2955     $updates->attemptsfinished = (object) array('updated' => false);
2956     $updates->attemptsunfinished = (object) array('updated' => false);
2957     $select = 'feedback = ? AND userid = ? AND timemodified > ?';
2958     $params = array($cm->instance, $USER->id, $from);
2960     $attemptsfinished = $DB->get_records_select('feedback_completed', $select, $params, '', 'id');
2961     if (!empty($attemptsfinished)) {
2962         $updates->attemptsfinished->updated = true;
2963         $updates->attemptsfinished->itemids = array_keys($attemptsfinished);
2964     }
2965     $attemptsunfinished = $DB->get_records_select('feedback_completedtmp', $select, $params, '', 'id');
2966     if (!empty($attemptsunfinished)) {
2967         $updates->attemptsunfinished->updated = true;
2968         $updates->attemptsunfinished->itemids = array_keys($attemptsunfinished);
2969     }
2971     // Now, teachers should see other students updates.
2972     if (has_capability('mod/feedback:viewreports', $cm->context)) {
2973         $select = 'feedback = ? AND timemodified > ?';
2974         $params = array($cm->instance, $from);
2976         if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
2977             $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
2978             if (empty($groupusers)) {
2979                 return $updates;
2980             }
2981             list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
2982             $select .= ' AND userid ' . $insql;
2983             $params = array_merge($params, $inparams);
2984         }
2986         $updates->userattemptsfinished = (object) array('updated' => false);
2987         $attemptsfinished = $DB->get_records_select('feedback_completed', $select, $params, '', 'id');
2988         if (!empty($attemptsfinished)) {
2989             $updates->userattemptsfinished->updated = true;
2990             $updates->userattemptsfinished->itemids = array_keys($attemptsfinished);
2991         }
2993         $updates->userattemptsunfinished = (object) array('updated' => false);
2994         $attemptsunfinished = $DB->get_records_select('feedback_completedtmp', $select, $params, '', 'id');
2995         if (!empty($attemptsunfinished)) {
2996             $updates->userattemptsunfinished->updated = true;
2997             $updates->userattemptsunfinished->itemids = array_keys($attemptsunfinished);
2998         }
2999     }
3001     return $updates;
3004 /**
3005  * This function receives a calendar event and returns the action associated with it, or null if there is none.
3006  *
3007  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
3008  * is not displayed on the block.
3009  *
3010  * @param calendar_event $event
3011  * @param \core_calendar\action_factory $factory
3012  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
3013  * @return \core_calendar\local\event\entities\action_interface|null
3014  */
3015 function mod_feedback_core_calendar_provide_event_action(calendar_event $event,
3016                                                          \core_calendar\action_factory $factory,
3017                                                          int $userid = 0) {
3019     global $USER;
3021     if (empty($userid)) {
3022         $userid = $USER->id;
3023     }
3025     $cm = get_fast_modinfo($event->courseid, $userid)->instances['feedback'][$event->instance];
3027     if (!$cm->uservisible) {
3028         // The module is not visible to the user for any reason.
3029         return null;
3030     }
3032     $feedbackcompletion = new mod_feedback_completion(null, $cm, 0, false, null, null, $userid);
3034     if (!empty($cm->customdata['timeclose']) && $cm->customdata['timeclose'] < time()) {
3035         // Feedback is already closed, do not display it even if it was never submitted.
3036         return null;
3037     }
3039     if (!$feedbackcompletion->can_complete()) {
3040         // The user can't complete the feedback so there is no action for them.
3041         return null;
3042     }
3044     // The feedback is actionable if it does not have timeopen or timeopen is in the past.
3045     $actionable = $feedbackcompletion->is_open();
3047     if ($actionable && $feedbackcompletion->is_already_submitted(false)) {
3048         // There is no need to display anything if the user has already submitted the feedback.
3049         return null;
3050     }
3052     return $factory->create_instance(
3053         get_string('answerquestions', 'feedback'),
3054         new \moodle_url('/mod/feedback/view.php', ['id' => $cm->id]),
3055         1,
3056         $actionable
3057     );
3060 /**
3061  * Add a get_coursemodule_info function in case any feedback type wants to add 'extra' information
3062  * for the course (see resource).
3063  *
3064  * Given a course_module object, this function returns any "extra" information that may be needed
3065  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
3066  *
3067  * @param stdClass $coursemodule The coursemodule object (record).
3068  * @return cached_cm_info An object on information that the courses
3069  *                        will know about (most noticeably, an icon).
3070  */
3071 function feedback_get_coursemodule_info($coursemodule) {
3072     global $DB;
3074     $dbparams = ['id' => $coursemodule->instance];
3075     $fields = 'id, name, intro, introformat, completionsubmit, timeopen, timeclose, anonymous';
3076     if (!$feedback = $DB->get_record('feedback', $dbparams, $fields)) {
3077         return false;
3078     }
3080     $result = new cached_cm_info();
3081     $result->name = $feedback->name;
3083     if ($coursemodule->showdescription) {
3084         // Convert intro to html. Do not filter cached version, filters run at display time.
3085         $result->content = format_module_intro('feedback', $feedback, $coursemodule->id, false);
3086     }
3088     // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
3089     if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
3090         $result->customdata['customcompletionrules']['completionsubmit'] = $feedback->completionsubmit;
3091     }
3092     // Populate some other values that can be used in calendar or on dashboard.
3093     if ($feedback->timeopen) {
3094         $result->customdata['timeopen'] = $feedback->timeopen;
3095     }
3096     if ($feedback->timeclose) {
3097         $result->customdata['timeclose'] = $feedback->timeclose;
3098     }
3099     if ($feedback->anonymous) {
3100         $result->customdata['anonymous'] = $feedback->anonymous;
3101     }
3103     return $result;
3106 /**
3107  * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
3108  *
3109  * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
3110  * @return array $descriptions the array of descriptions for the custom rules.
3111  */
3112 function mod_feedback_get_completion_active_rule_descriptions($cm) {
3113     // Values will be present in cm_info, and we assume these are up to date.
3114     if (empty($cm->customdata['customcompletionrules'])
3115         || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
3116         return [];
3117     }
3119     $descriptions = [];
3120     foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
3121         switch ($key) {
3122             case 'completionsubmit':
3123                 if (!empty($val)) {
3124                     $descriptions[] = get_string('completionsubmit', 'feedback');
3125                 }
3126                 break;
3127             default:
3128                 break;
3129         }
3130     }
3131     return $descriptions;
3134 /**
3135  * This function calculates the minimum and maximum cutoff values for the timestart of
3136  * the given event.
3137  *
3138  * It will return an array with two values, the first being the minimum cutoff value and
3139  * the second being the maximum cutoff value. Either or both values can be null, which
3140  * indicates there is no minimum or maximum, respectively.
3141  *
3142  * If a cutoff is required then the function must return an array containing the cutoff
3143  * timestamp and error string to display to the user if the cutoff value is violated.
3144  *
3145  * A minimum and maximum cutoff return value will look like:
3146  * [
3147  *     [1505704373, 'The due date must be after the sbumission start date'],
3148  *     [1506741172, 'The due date must be before the cutoff date']
3149  * ]
3150  *
3151  * @param calendar_event $event The calendar event to get the time range for
3152  * @param stdClass $instance The module instance to get the range from
3153  * @return array
3154  */
3155 function mod_feedback_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
3156     $mindate = null;
3157     $maxdate = null;
3159     if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) {
3160         // The start time of the open event can't be equal to or after the
3161         // close time of the choice activity.
3162         if (!empty($instance->timeclose)) {
3163             $maxdate = [
3164                 $instance->timeclose,
3165                 get_string('openafterclose', 'feedback')
3166             ];
3167         }
3168     } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) {
3169         // The start time of the close event can't be equal to or earlier than the
3170         // open time of the choice activity.
3171         if (!empty($instance->timeopen)) {
3172             $mindate = [
3173                 $instance->timeopen,
3174                 get_string('closebeforeopen', 'feedback')
3175             ];
3176         }
3177     }
3179     return [$mindate, $maxdate];
3182 /**
3183  * This function will update the feedback module according to the
3184  * event that has been modified.
3185  *
3186  * It will set the timeopen or timeclose value of the feedback instance
3187  * according to the type of event provided.
3188  *
3189  * @throws \moodle_exception
3190  * @param \calendar_event $event
3191  * @param stdClass $feedback The module instance to get the range from
3192  */
3193 function mod_feedback_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $feedback) {
3194     global $CFG, $DB;
3196     if (empty($event->instance) || $event->modulename != 'feedback') {
3197         return;
3198     }
3200     if ($event->instance != $feedback->id) {
3201         return;
3202     }
3204     if (!in_array($event->eventtype, [FEEDBACK_EVENT_TYPE_OPEN, FEEDBACK_EVENT_TYPE_CLOSE])) {
3205         return;
3206     }
3208     $courseid = $event->courseid;
3209     $modulename = $event->modulename;
3210     $instanceid = $event->instance;
3211     $modified = false;
3213     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
3214     $context = context_module::instance($coursemodule->id);
3216     // The user does not have the capability to modify this activity.
3217     if (!has_capability('moodle/course:manageactivities', $context)) {
3218         return;
3219     }
3221     if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) {
3222         // If the event is for the feedback activity opening then we should
3223         // set the start time of the feedback activity to be the new start
3224         // time of the event.
3225         if ($feedback->timeopen != $event->timestart) {
3226             $feedback->timeopen = $event->timestart;
3227             $feedback->timemodified = time();
3228             $modified = true;
3229         }
3230     } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) {
3231         // If the event is for the feedback activity closing then we should
3232         // set the end time of the feedback activity to be the new start
3233         // time of the event.
3234         if ($feedback->timeclose != $event->timestart) {
3235             $feedback->timeclose = $event->timestart;
3236             $modified = true;
3237         }
3238     }
3240     if ($modified) {
3241         $feedback->timemodified = time();
3242         $DB->update_record('feedback', $feedback);
3243         $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
3244         $event->trigger();
3245     }