Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

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