Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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