Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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.
/mod/choice/ -> lib.php (source)

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

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package   mod_choice
  20   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  21   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  /** @global int $CHOICE_COLUMN_HEIGHT */
  27  global $CHOICE_COLUMN_HEIGHT;
  28  $CHOICE_COLUMN_HEIGHT = 300;
  29  
  30  /** @global int $CHOICE_COLUMN_WIDTH */
  31  global $CHOICE_COLUMN_WIDTH;
  32  $CHOICE_COLUMN_WIDTH = 300;
  33  
  34  define('CHOICE_PUBLISH_ANONYMOUS', '0');
  35  define('CHOICE_PUBLISH_NAMES',     '1');
  36  
  37  define('CHOICE_SHOWRESULTS_NOT',          '0');
  38  define('CHOICE_SHOWRESULTS_AFTER_ANSWER', '1');
  39  define('CHOICE_SHOWRESULTS_AFTER_CLOSE',  '2');
  40  define('CHOICE_SHOWRESULTS_ALWAYS',       '3');
  41  
  42  define('CHOICE_DISPLAY_HORIZONTAL',  '0');
  43  define('CHOICE_DISPLAY_VERTICAL',    '1');
  44  
  45  define('CHOICE_EVENT_TYPE_OPEN', 'open');
  46  define('CHOICE_EVENT_TYPE_CLOSE', 'close');
  47  
  48  /** @global array $CHOICE_PUBLISH */
  49  global $CHOICE_PUBLISH;
  50  $CHOICE_PUBLISH = array (CHOICE_PUBLISH_ANONYMOUS  => get_string('publishanonymous', 'choice'),
  51                           CHOICE_PUBLISH_NAMES      => get_string('publishnames', 'choice'));
  52  
  53  /** @global array $CHOICE_SHOWRESULTS */
  54  global $CHOICE_SHOWRESULTS;
  55  $CHOICE_SHOWRESULTS = array (CHOICE_SHOWRESULTS_NOT          => get_string('publishnot', 'choice'),
  56                           CHOICE_SHOWRESULTS_AFTER_ANSWER => get_string('publishafteranswer', 'choice'),
  57                           CHOICE_SHOWRESULTS_AFTER_CLOSE  => get_string('publishafterclose', 'choice'),
  58                           CHOICE_SHOWRESULTS_ALWAYS       => get_string('publishalways', 'choice'));
  59  
  60  /** @global array $CHOICE_DISPLAY */
  61  global $CHOICE_DISPLAY;
  62  $CHOICE_DISPLAY = array (CHOICE_DISPLAY_HORIZONTAL   => get_string('displayhorizontal', 'choice'),
  63                           CHOICE_DISPLAY_VERTICAL     => get_string('displayvertical','choice'));
  64  
  65  /// Standard functions /////////////////////////////////////////////////////////
  66  
  67  /**
  68   * @global object
  69   * @param object $course
  70   * @param object $user
  71   * @param object $mod
  72   * @param object $choice
  73   * @return object|null
  74   */
  75  function choice_user_outline($course, $user, $mod, $choice) {
  76      global $DB;
  77      if ($answer = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $user->id))) {
  78          $result = new stdClass();
  79          $result->info = "'".format_string(choice_get_option_text($choice, $answer->optionid))."'";
  80          $result->time = $answer->timemodified;
  81          return $result;
  82      }
  83      return NULL;
  84  }
  85  
  86  /**
  87   * Callback for the "Complete" report - prints the activity summary for the given user
  88   *
  89   * @param object $course
  90   * @param object $user
  91   * @param object $mod
  92   * @param object $choice
  93   */
  94  function choice_user_complete($course, $user, $mod, $choice) {
  95      global $DB;
  96      if ($answers = $DB->get_records('choice_answers', array("choiceid" => $choice->id, "userid" => $user->id))) {
  97          $info = [];
  98          foreach ($answers as $answer) {
  99              $info[] = "'" . format_string(choice_get_option_text($choice, $answer->optionid)) . "'";
 100          }
 101          core_collator::asort($info);
 102          echo get_string("answered", "choice") . ": ". join(', ', $info) . ". " .
 103                  get_string("updated", '', userdate($answer->timemodified));
 104      } else {
 105          print_string("notanswered", "choice");
 106      }
 107  }
 108  
 109  /**
 110   * Given an object containing all the necessary data,
 111   * (defined by the form in mod_form.php) this function
 112   * will create a new instance and return the id number
 113   * of the new instance.
 114   *
 115   * @global object
 116   * @param object $choice
 117   * @return int
 118   */
 119  function choice_add_instance($choice) {
 120      global $DB, $CFG;
 121      require_once($CFG->dirroot.'/mod/choice/locallib.php');
 122  
 123      $choice->timemodified = time();
 124  
 125      //insert answers
 126      $choice->id = $DB->insert_record("choice", $choice);
 127      foreach ($choice->option as $key => $value) {
 128          $value = trim($value);
 129          if (isset($value) && $value <> '') {
 130              $option = new stdClass();
 131              $option->text = $value;
 132              $option->choiceid = $choice->id;
 133              if (isset($choice->limit[$key])) {
 134                  $option->maxanswers = $choice->limit[$key];
 135              }
 136              $option->timemodified = time();
 137              $DB->insert_record("choice_options", $option);
 138          }
 139      }
 140  
 141      // Add calendar events if necessary.
 142      choice_set_events($choice);
 143      if (!empty($choice->completionexpected)) {
 144          \core_completion\api::update_completion_date_event($choice->coursemodule, 'choice', $choice->id,
 145                  $choice->completionexpected);
 146      }
 147  
 148      return $choice->id;
 149  }
 150  
 151  /**
 152   * Given an object containing all the necessary data,
 153   * (defined by the form in mod_form.php) this function
 154   * will update an existing instance with new data.
 155   *
 156   * @global object
 157   * @param object $choice
 158   * @return bool
 159   */
 160  function choice_update_instance($choice) {
 161      global $DB, $CFG;
 162      require_once($CFG->dirroot.'/mod/choice/locallib.php');
 163  
 164      $choice->id = $choice->instance;
 165      $choice->timemodified = time();
 166  
 167      //update, delete or insert answers
 168      foreach ($choice->option as $key => $value) {
 169          $value = trim($value);
 170          $option = new stdClass();
 171          $option->text = $value;
 172          $option->choiceid = $choice->id;
 173          if (isset($choice->limit[$key])) {
 174              $option->maxanswers = $choice->limit[$key];
 175          }
 176          $option->timemodified = time();
 177          if (isset($choice->optionid[$key]) && !empty($choice->optionid[$key])){//existing choice record
 178              $option->id=$choice->optionid[$key];
 179              if (isset($value) && $value <> '') {
 180                  $DB->update_record("choice_options", $option);
 181              } else {
 182                  // Remove the empty (unused) option.
 183                  $DB->delete_records("choice_options", array("id" => $option->id));
 184                  // Delete any answers associated with this option.
 185                  $DB->delete_records("choice_answers", array("choiceid" => $choice->id, "optionid" => $option->id));
 186              }
 187          } else {
 188              if (isset($value) && $value <> '') {
 189                  $DB->insert_record("choice_options", $option);
 190              }
 191          }
 192      }
 193  
 194      // Add calendar events if necessary.
 195      choice_set_events($choice);
 196      $completionexpected = (!empty($choice->completionexpected)) ? $choice->completionexpected : null;
 197      \core_completion\api::update_completion_date_event($choice->coursemodule, 'choice', $choice->id, $completionexpected);
 198  
 199      return $DB->update_record('choice', $choice);
 200  
 201  }
 202  
 203  /**
 204   * @global object
 205   * @param object $choice
 206   * @param object $user
 207   * @param object $coursemodule
 208   * @param array $allresponses
 209   * @return array
 210   */
 211  function choice_prepare_options($choice, $user, $coursemodule, $allresponses) {
 212      global $DB;
 213  
 214      $cdisplay = array('options'=>array());
 215  
 216      $cdisplay['limitanswers'] = $choice->limitanswers;
 217      $cdisplay['showavailable'] = $choice->showavailable;
 218  
 219      $context = context_module::instance($coursemodule->id);
 220  
 221      foreach ($choice->option as $optionid => $text) {
 222          if (isset($text)) { //make sure there are no dud entries in the db with blank text values.
 223              $option = new stdClass;
 224              $option->attributes = new stdClass;
 225              $option->attributes->value = $optionid;
 226              $option->text = format_string($text);
 227              $option->maxanswers = $choice->maxanswers[$optionid];
 228              $option->displaylayout = $choice->display;
 229  
 230              if (isset($allresponses[$optionid])) {
 231                  $option->countanswers = count($allresponses[$optionid]);
 232              } else {
 233                  $option->countanswers = 0;
 234              }
 235              if ($DB->record_exists('choice_answers', array('choiceid' => $choice->id, 'userid' => $user->id, 'optionid' => $optionid))) {
 236                  $option->attributes->checked = true;
 237              }
 238              if ( $choice->limitanswers && ($option->countanswers >= $option->maxanswers) && empty($option->attributes->checked)) {
 239                  $option->attributes->disabled = true;
 240              }
 241              $cdisplay['options'][] = $option;
 242          }
 243      }
 244  
 245      $cdisplay['hascapability'] = is_enrolled($context, NULL, 'mod/choice:choose'); //only enrolled users are allowed to make a choice
 246  
 247      if ($choice->allowupdate && $DB->record_exists('choice_answers', array('choiceid'=> $choice->id, 'userid'=> $user->id))) {
 248          $cdisplay['allowupdate'] = true;
 249      }
 250  
 251      if ($choice->showpreview && $choice->timeopen > time()) {
 252          $cdisplay['previewonly'] = true;
 253      }
 254  
 255      return $cdisplay;
 256  }
 257  
 258  /**
 259   * Modifies responses of other users adding the option $newoptionid to them
 260   *
 261   * @param array $userids list of users to add option to (must be users without any answers yet)
 262   * @param array $answerids list of existing attempt ids of users (will be either appended or
 263   *      substituted with the newoptionid, depending on $choice->allowmultiple)
 264   * @param int $newoptionid
 265   * @param stdClass $choice choice object, result of {@link choice_get_choice()}
 266   * @param stdClass $cm
 267   * @param stdClass $course
 268   */
 269  function choice_modify_responses($userids, $answerids, $newoptionid, $choice, $cm, $course) {
 270      // Get all existing responses and the list of non-respondents.
 271      $groupmode = groups_get_activity_groupmode($cm);
 272      $onlyactive = $choice->includeinactive ? false : true;
 273      $allresponses = choice_get_response_data($choice, $cm, $groupmode, $onlyactive);
 274  
 275      // Check that the option value is valid.
 276      if (!$newoptionid || !isset($choice->option[$newoptionid])) {
 277          return;
 278      }
 279  
 280      // First add responses for users who did not make any choice yet.
 281      foreach ($userids as $userid) {
 282          if (isset($allresponses[0][$userid])) {
 283              choice_user_submit_response($newoptionid, $choice, $userid, $course, $cm);
 284          }
 285      }
 286  
 287      // Create the list of all options already selected by each user.
 288      $optionsbyuser = []; // Mapping userid=>array of chosen choice options.
 289      $usersbyanswer = []; // Mapping answerid=>userid (which answer belongs to each user).
 290      foreach ($allresponses as $optionid => $responses) {
 291          if ($optionid > 0) {
 292              foreach ($responses as $userid => $userresponse) {
 293                  $optionsbyuser += [$userid => []];
 294                  $optionsbyuser[$userid][] = $optionid;
 295                  $usersbyanswer[$userresponse->answerid] = $userid;
 296              }
 297          }
 298      }
 299  
 300      // Go through the list of submitted attemptids and find which users answers need to be updated.
 301      foreach ($answerids as $answerid) {
 302          if (isset($usersbyanswer[$answerid])) {
 303              $userid = $usersbyanswer[$answerid];
 304              if (!in_array($newoptionid, $optionsbyuser[$userid])) {
 305                  $options = $choice->allowmultiple ?
 306                          array_merge($optionsbyuser[$userid], [$newoptionid]) : $newoptionid;
 307                  choice_user_submit_response($options, $choice, $userid, $course, $cm);
 308              }
 309          }
 310      }
 311  }
 312  
 313  /**
 314   * Process user submitted answers for a choice,
 315   * and either updating them or saving new answers.
 316   *
 317   * @param int|array $formanswer the id(s) of the user submitted choice options.
 318   * @param object $choice the selected choice.
 319   * @param int $userid user identifier.
 320   * @param object $course current course.
 321   * @param object $cm course context.
 322   * @return void
 323   */
 324  function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm) {
 325      global $DB, $CFG, $USER;
 326      require_once($CFG->libdir.'/completionlib.php');
 327  
 328      $continueurl = new moodle_url('/mod/choice/view.php', array('id' => $cm->id));
 329  
 330      if (empty($formanswer)) {
 331          print_error('atleastoneoption', 'choice', $continueurl);
 332      }
 333  
 334      if (is_array($formanswer)) {
 335          if (!$choice->allowmultiple) {
 336              print_error('multiplenotallowederror', 'choice', $continueurl);
 337          }
 338          $formanswers = $formanswer;
 339      } else {
 340          $formanswers = array($formanswer);
 341      }
 342  
 343      $options = $DB->get_records('choice_options', array('choiceid' => $choice->id), '', 'id');
 344      foreach ($formanswers as $key => $val) {
 345          if (!isset($options[$val])) {
 346              print_error('cannotsubmit', 'choice', $continueurl);
 347          }
 348      }
 349      // Start lock to prevent synchronous access to the same data
 350      // before it's updated, if using limits.
 351      if ($choice->limitanswers) {
 352          $timeout = 10;
 353          $locktype = 'mod_choice_choice_user_submit_response';
 354          // Limiting access to this choice.
 355          $resouce = 'choiceid:' . $choice->id;
 356          $lockfactory = \core\lock\lock_config::get_lock_factory($locktype);
 357  
 358          // Opening the lock.
 359          $choicelock = $lockfactory->get_lock($resouce, $timeout, MINSECS);
 360          if (!$choicelock) {
 361              print_error('cannotsubmit', 'choice', $continueurl);
 362          }
 363      }
 364  
 365      $current = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid));
 366  
 367      // Array containing [answerid => optionid] mapping.
 368      $existinganswers = array_map(function($answer) {
 369          return $answer->optionid;
 370      }, $current);
 371  
 372      $context = context_module::instance($cm->id);
 373  
 374      $choicesexceeded = false;
 375      $countanswers = array();
 376      foreach ($formanswers as $val) {
 377          $countanswers[$val] = 0;
 378      }
 379      if($choice->limitanswers) {
 380          // Find out whether groups are being used and enabled
 381          if (groups_get_activity_groupmode($cm) > 0) {
 382              $currentgroup = groups_get_activity_group($cm);
 383          } else {
 384              $currentgroup = 0;
 385          }
 386  
 387          list ($insql, $params) = $DB->get_in_or_equal($formanswers, SQL_PARAMS_NAMED);
 388  
 389          if($currentgroup) {
 390              // If groups are being used, retrieve responses only for users in
 391              // current group
 392              global $CFG;
 393  
 394              $params['groupid'] = $currentgroup;
 395              $sql = "SELECT ca.*
 396                        FROM {choice_answers} ca
 397                  INNER JOIN {groups_members} gm ON ca.userid=gm.userid
 398                       WHERE optionid $insql
 399                         AND gm.groupid= :groupid";
 400          } else {
 401              // Groups are not used, retrieve all answers for this option ID
 402              $sql = "SELECT ca.*
 403                        FROM {choice_answers} ca
 404                       WHERE optionid $insql";
 405          }
 406  
 407          $answers = $DB->get_records_sql($sql, $params);
 408          if ($answers) {
 409              foreach ($answers as $a) { //only return enrolled users.
 410                  if (is_enrolled($context, $a->userid, 'mod/choice:choose')) {
 411                      $countanswers[$a->optionid]++;
 412                  }
 413              }
 414          }
 415  
 416          foreach ($countanswers as $opt => $count) {
 417              // Ignore the user's existing answers when checking whether an answer count has been exceeded.
 418              // A user may wish to update their response with an additional choice option and shouldn't be competing with themself!
 419              if (in_array($opt, $existinganswers)) {
 420                  continue;
 421              }
 422              if ($count >= $choice->maxanswers[$opt]) {
 423                  $choicesexceeded = true;
 424                  break;
 425              }
 426          }
 427      }
 428  
 429      // Check the user hasn't exceeded the maximum selections for the choice(s) they have selected.
 430      $answersnapshots = array();
 431      $deletedanswersnapshots = array();
 432      if (!($choice->limitanswers && $choicesexceeded)) {
 433          if ($current) {
 434              // Update an existing answer.
 435              foreach ($current as $c) {
 436                  if (in_array($c->optionid, $formanswers)) {
 437                      $DB->set_field('choice_answers', 'timemodified', time(), array('id' => $c->id));
 438                  } else {
 439                      $deletedanswersnapshots[] = $c;
 440                      $DB->delete_records('choice_answers', array('id' => $c->id));
 441                  }
 442              }
 443  
 444              // Add new ones.
 445              foreach ($formanswers as $f) {
 446                  if (!in_array($f, $existinganswers)) {
 447                      $newanswer = new stdClass();
 448                      $newanswer->optionid = $f;
 449                      $newanswer->choiceid = $choice->id;
 450                      $newanswer->userid = $userid;
 451                      $newanswer->timemodified = time();
 452                      $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
 453                      $answersnapshots[] = $newanswer;
 454                  }
 455              }
 456          } else {
 457              // Add new answer.
 458              foreach ($formanswers as $answer) {
 459                  $newanswer = new stdClass();
 460                  $newanswer->choiceid = $choice->id;
 461                  $newanswer->userid = $userid;
 462                  $newanswer->optionid = $answer;
 463                  $newanswer->timemodified = time();
 464                  $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
 465                  $answersnapshots[] = $newanswer;
 466              }
 467  
 468              // Update completion state
 469              $completion = new completion_info($course);
 470              if ($completion->is_enabled($cm) && $choice->completionsubmit) {
 471                  $completion->update_state($cm, COMPLETION_COMPLETE);
 472              }
 473          }
 474      } else {
 475          // This is a choice with limited options, and one of the options selected has just run over its limit.
 476          $choicelock->release();
 477          print_error('choicefull', 'choice', $continueurl);
 478      }
 479  
 480      // Release lock.
 481      if (isset($choicelock)) {
 482          $choicelock->release();
 483      }
 484  
 485      // Trigger events.
 486      foreach ($deletedanswersnapshots as $answer) {
 487          \mod_choice\event\answer_deleted::create_from_object($answer, $choice, $cm, $course)->trigger();
 488      }
 489      foreach ($answersnapshots as $answer) {
 490          \mod_choice\event\answer_created::create_from_object($answer, $choice, $cm, $course)->trigger();
 491      }
 492  }
 493  
 494  /**
 495   * @param array $user
 496   * @param object $cm
 497   * @return void Output is echo'd
 498   */
 499  function choice_show_reportlink($user, $cm) {
 500      $userschosen = array();
 501      foreach($user as $optionid => $userlist) {
 502          if ($optionid) {
 503              $userschosen = array_merge($userschosen, array_keys($userlist));
 504          }
 505      }
 506      $responsecount = count(array_unique($userschosen));
 507  
 508      echo '<div class="reportlink">';
 509      echo "<a href=\"report.php?id=$cm->id\">".get_string("viewallresponses", "choice", $responsecount)."</a>";
 510      echo '</div>';
 511  }
 512  
 513  /**
 514   * @global object
 515   * @param object $choice
 516   * @param object $course
 517   * @param object $coursemodule
 518   * @param array $allresponses
 519  
 520   *  * @param bool $allresponses
 521   * @return object
 522   */
 523  function prepare_choice_show_results($choice, $course, $cm, $allresponses) {
 524      global $OUTPUT;
 525  
 526      $display = clone($choice);
 527      $display->coursemoduleid = $cm->id;
 528      $display->courseid = $course->id;
 529  
 530      if (!empty($choice->showunanswered)) {
 531          $choice->option[0] = get_string('notanswered', 'choice');
 532          $choice->maxanswers[0] = 0;
 533      }
 534  
 535      // Remove from the list of non-respondents the users who do not have access to this activity.
 536      if (!empty($display->showunanswered) && $allresponses[0]) {
 537          $info = new \core_availability\info_module(cm_info::create($cm));
 538          $allresponses[0] = $info->filter_user_list($allresponses[0]);
 539      }
 540  
 541      //overwrite options value;
 542      $display->options = array();
 543      $allusers = [];
 544      foreach ($choice->option as $optionid => $optiontext) {
 545          $display->options[$optionid] = new stdClass;
 546          $display->options[$optionid]->text = format_string($optiontext, true,
 547              ['context' => context_module::instance($cm->id)]);
 548          $display->options[$optionid]->maxanswer = $choice->maxanswers[$optionid];
 549  
 550          if (array_key_exists($optionid, $allresponses)) {
 551              $display->options[$optionid]->user = $allresponses[$optionid];
 552              $allusers = array_merge($allusers, array_keys($allresponses[$optionid]));
 553          }
 554      }
 555      unset($display->option);
 556      unset($display->maxanswers);
 557  
 558      $display->numberofuser = count(array_unique($allusers));
 559      $context = context_module::instance($cm->id);
 560      $display->viewresponsecapability = has_capability('mod/choice:readresponses', $context);
 561      $display->deleterepsonsecapability = has_capability('mod/choice:deleteresponses',$context);
 562      $display->fullnamecapability = has_capability('moodle/site:viewfullnames', $context);
 563  
 564      if (empty($allresponses)) {
 565          echo $OUTPUT->heading(get_string("nousersyet"), 3, null);
 566          return false;
 567      }
 568  
 569      return $display;
 570  }
 571  
 572  /**
 573   * @global object
 574   * @param array $attemptids
 575   * @param object $choice Choice main table row
 576   * @param object $cm Course-module object
 577   * @param object $course Course object
 578   * @return bool
 579   */
 580  function choice_delete_responses($attemptids, $choice, $cm, $course) {
 581      global $DB, $CFG, $USER;
 582      require_once($CFG->libdir.'/completionlib.php');
 583  
 584      if(!is_array($attemptids) || empty($attemptids)) {
 585          return false;
 586      }
 587  
 588      foreach($attemptids as $num => $attemptid) {
 589          if(empty($attemptid)) {
 590              unset($attemptids[$num]);
 591          }
 592      }
 593  
 594      $completion = new completion_info($course);
 595      foreach($attemptids as $attemptid) {
 596          if ($todelete = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid))) {
 597              // Trigger the event answer deleted.
 598              \mod_choice\event\answer_deleted::create_from_object($todelete, $choice, $cm, $course)->trigger();
 599              $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid));
 600          }
 601      }
 602  
 603      // Update completion state.
 604      if ($completion->is_enabled($cm) && $choice->completionsubmit) {
 605          $completion->update_state($cm, COMPLETION_INCOMPLETE);
 606      }
 607  
 608      return true;
 609  }
 610  
 611  
 612  /**
 613   * Given an ID of an instance of this module,
 614   * this function will permanently delete the instance
 615   * and any data that depends on it.
 616   *
 617   * @global object
 618   * @param int $id
 619   * @return bool
 620   */
 621  function choice_delete_instance($id) {
 622      global $DB;
 623  
 624      if (! $choice = $DB->get_record("choice", array("id"=>"$id"))) {
 625          return false;
 626      }
 627  
 628      $result = true;
 629  
 630      if (! $DB->delete_records("choice_answers", array("choiceid"=>"$choice->id"))) {
 631          $result = false;
 632      }
 633  
 634      if (! $DB->delete_records("choice_options", array("choiceid"=>"$choice->id"))) {
 635          $result = false;
 636      }
 637  
 638      if (! $DB->delete_records("choice", array("id"=>"$choice->id"))) {
 639          $result = false;
 640      }
 641      // Remove old calendar events.
 642      if (! $DB->delete_records('event', array('modulename' => 'choice', 'instance' => $choice->id))) {
 643          $result = false;
 644      }
 645  
 646      return $result;
 647  }
 648  
 649  /**
 650   * Returns text string which is the answer that matches the id
 651   *
 652   * @global object
 653   * @param object $choice
 654   * @param int $id
 655   * @return string
 656   */
 657  function choice_get_option_text($choice, $id) {
 658      global $DB;
 659  
 660      if ($result = $DB->get_record("choice_options", array("id" => $id))) {
 661          return $result->text;
 662      } else {
 663          return get_string("notanswered", "choice");
 664      }
 665  }
 666  
 667  /**
 668   * Gets a full choice record
 669   *
 670   * @global object
 671   * @param int $choiceid
 672   * @return object|bool The choice or false
 673   */
 674  function choice_get_choice($choiceid) {
 675      global $DB;
 676  
 677      if ($choice = $DB->get_record("choice", array("id" => $choiceid))) {
 678          if ($options = $DB->get_records("choice_options", array("choiceid" => $choiceid), "id")) {
 679              foreach ($options as $option) {
 680                  $choice->option[$option->id] = $option->text;
 681                  $choice->maxanswers[$option->id] = $option->maxanswers;
 682              }
 683              return $choice;
 684          }
 685      }
 686      return false;
 687  }
 688  
 689  /**
 690   * List the actions that correspond to a view of this module.
 691   * This is used by the participation report.
 692   *
 693   * Note: This is not used by new logging system. Event with
 694   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
 695   *       be considered as view action.
 696   *
 697   * @return array
 698   */
 699  function choice_get_view_actions() {
 700      return array('view','view all','report');
 701  }
 702  
 703  /**
 704   * List the actions that correspond to a post of this module.
 705   * This is used by the participation report.
 706   *
 707   * Note: This is not used by new logging system. Event with
 708   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
 709   *       will be considered as post action.
 710   *
 711   * @return array
 712   */
 713  function choice_get_post_actions() {
 714      return array('choose','choose again');
 715  }
 716  
 717  
 718  /**
 719   * Implementation of the function for printing the form elements that control
 720   * whether the course reset functionality affects the choice.
 721   *
 722   * @param object $mform form passed by reference
 723   */
 724  function choice_reset_course_form_definition(&$mform) {
 725      $mform->addElement('header', 'choiceheader', get_string('modulenameplural', 'choice'));
 726      $mform->addElement('advcheckbox', 'reset_choice', get_string('removeresponses','choice'));
 727  }
 728  
 729  /**
 730   * Course reset form defaults.
 731   *
 732   * @return array
 733   */
 734  function choice_reset_course_form_defaults($course) {
 735      return array('reset_choice'=>1);
 736  }
 737  
 738  /**
 739   * Actual implementation of the reset course functionality, delete all the
 740   * choice responses for course $data->courseid.
 741   *
 742   * @global object
 743   * @global object
 744   * @param object $data the data submitted from the reset course.
 745   * @return array status array
 746   */
 747  function choice_reset_userdata($data) {
 748      global $CFG, $DB;
 749  
 750      $componentstr = get_string('modulenameplural', 'choice');
 751      $status = array();
 752  
 753      if (!empty($data->reset_choice)) {
 754          $choicessql = "SELECT ch.id
 755                         FROM {choice} ch
 756                         WHERE ch.course=?";
 757  
 758          $DB->delete_records_select('choice_answers', "choiceid IN ($choicessql)", array($data->courseid));
 759          $status[] = array('component'=>$componentstr, 'item'=>get_string('removeresponses', 'choice'), 'error'=>false);
 760      }
 761  
 762      /// updating dates - shift may be negative too
 763      if ($data->timeshift) {
 764          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
 765          // See MDL-9367.
 766          shift_course_mod_dates('choice', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid);
 767          $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
 768      }
 769  
 770      return $status;
 771  }
 772  
 773  /**
 774   * @global object
 775   * @global object
 776   * @global object
 777   * @uses CONTEXT_MODULE
 778   * @param object $choice
 779   * @param object $cm
 780   * @param int $groupmode
 781   * @param bool $onlyactive Whether to get response data for active users only.
 782   * @return array
 783   */
 784  function choice_get_response_data($choice, $cm, $groupmode, $onlyactive) {
 785      global $CFG, $USER, $DB;
 786  
 787      $context = context_module::instance($cm->id);
 788  
 789  /// Get the current group
 790      if ($groupmode > 0) {
 791          $currentgroup = groups_get_activity_group($cm);
 792      } else {
 793          $currentgroup = 0;
 794      }
 795  
 796  /// Initialise the returned array, which is a matrix:  $allresponses[responseid][userid] = responseobject
 797      $allresponses = array();
 798  
 799  /// First get all the users who have access here
 800  /// To start with we assume they are all "unanswered" then move them later
 801      $extrafields = get_extra_user_fields($context);
 802      $allresponses[0] = get_enrolled_users($context, 'mod/choice:choose', $currentgroup,
 803              user_picture::fields('u', $extrafields), null, 0, 0, $onlyactive);
 804  
 805  /// Get all the recorded responses for this choice
 806      $rawresponses = $DB->get_records('choice_answers', array('choiceid' => $choice->id));
 807  
 808  /// Use the responses to move users into the correct column
 809  
 810      if ($rawresponses) {
 811          $answeredusers = array();
 812          foreach ($rawresponses as $response) {
 813              if (isset($allresponses[0][$response->userid])) {   // This person is enrolled and in correct group
 814                  $allresponses[0][$response->userid]->timemodified = $response->timemodified;
 815                  $allresponses[$response->optionid][$response->userid] = clone($allresponses[0][$response->userid]);
 816                  $allresponses[$response->optionid][$response->userid]->answerid = $response->id;
 817                  $answeredusers[] = $response->userid;
 818              }
 819          }
 820          foreach ($answeredusers as $answereduser) {
 821              unset($allresponses[0][$answereduser]);
 822          }
 823      }
 824      return $allresponses;
 825  }
 826  
 827  /**
 828   * @uses FEATURE_GROUPS
 829   * @uses FEATURE_GROUPINGS
 830   * @uses FEATURE_MOD_INTRO
 831   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
 832   * @uses FEATURE_GRADE_HAS_GRADE
 833   * @uses FEATURE_GRADE_OUTCOMES
 834   * @param string $feature FEATURE_xx constant for requested feature
 835   * @return mixed True if module supports feature, null if doesn't know
 836   */
 837  function choice_supports($feature) {
 838      switch($feature) {
 839          case FEATURE_GROUPS:                  return true;
 840          case FEATURE_GROUPINGS:               return true;
 841          case FEATURE_MOD_INTRO:               return true;
 842          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
 843          case FEATURE_COMPLETION_HAS_RULES:    return true;
 844          case FEATURE_GRADE_HAS_GRADE:         return false;
 845          case FEATURE_GRADE_OUTCOMES:          return false;
 846          case FEATURE_BACKUP_MOODLE2:          return true;
 847          case FEATURE_SHOW_DESCRIPTION:        return true;
 848  
 849          default: return null;
 850      }
 851  }
 852  
 853  /**
 854   * Adds module specific settings to the settings block
 855   *
 856   * @param settings_navigation $settings The settings navigation object
 857   * @param navigation_node $choicenode The node to add module settings to
 858   */
 859  function choice_extend_settings_navigation(settings_navigation $settings, navigation_node $choicenode) {
 860      global $PAGE;
 861  
 862      if (has_capability('mod/choice:readresponses', $PAGE->cm->context)) {
 863  
 864          $groupmode = groups_get_activity_groupmode($PAGE->cm);
 865          if ($groupmode) {
 866              groups_get_activity_group($PAGE->cm, true);
 867          }
 868  
 869          $choice = choice_get_choice($PAGE->cm->instance);
 870  
 871          // Check if we want to include responses from inactive users.
 872          $onlyactive = $choice->includeinactive ? false : true;
 873  
 874          // Big function, approx 6 SQL calls per user.
 875          $allresponses = choice_get_response_data($choice, $PAGE->cm, $groupmode, $onlyactive);
 876  
 877          $allusers = [];
 878          foreach($allresponses as $optionid => $userlist) {
 879              if ($optionid) {
 880                  $allusers = array_merge($allusers, array_keys($userlist));
 881              }
 882          }
 883          $responsecount = count(array_unique($allusers));
 884          $choicenode->add(get_string("viewallresponses", "choice", $responsecount), new moodle_url('/mod/choice/report.php', array('id'=>$PAGE->cm->id)));
 885      }
 886  }
 887  
 888  /**
 889   * Obtains the automatic completion state for this choice based on any conditions
 890   * in forum settings.
 891   *
 892   * @param object $course Course
 893   * @param object $cm Course-module
 894   * @param int $userid User ID
 895   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
 896   * @return bool True if completed, false if not, $type if conditions not set.
 897   */
 898  function choice_get_completion_state($course, $cm, $userid, $type) {
 899      global $CFG,$DB;
 900  
 901      // Get choice details
 902      $choice = $DB->get_record('choice', array('id'=>$cm->instance), '*',
 903              MUST_EXIST);
 904  
 905      // If completion option is enabled, evaluate it and return true/false
 906      if($choice->completionsubmit) {
 907          return $DB->record_exists('choice_answers', array(
 908                  'choiceid'=>$choice->id, 'userid'=>$userid));
 909      } else {
 910          // Completion option is not enabled so just return $type
 911          return $type;
 912      }
 913  }
 914  
 915  /**
 916   * Return a list of page types
 917   * @param string $pagetype current page type
 918   * @param stdClass $parentcontext Block's parent context
 919   * @param stdClass $currentcontext Current context of block
 920   */
 921  function choice_page_type_list($pagetype, $parentcontext, $currentcontext) {
 922      $module_pagetype = array('mod-choice-*'=>get_string('page-mod-choice-x', 'choice'));
 923      return $module_pagetype;
 924  }
 925  
 926  /**
 927   * @deprecated since Moodle 3.3, when the block_course_overview block was removed.
 928   */
 929  function choice_print_overview() {
 930      throw new coding_exception('choice_print_overview() can not be used any more and is obsolete.');
 931  }
 932  
 933  
 934  /**
 935   * Get responses of a given user on a given choice.
 936   *
 937   * @param stdClass $choice Choice record
 938   * @param int $userid User id
 939   * @return array of choice answers records
 940   * @since  Moodle 3.6
 941   */
 942  function choice_get_user_response($choice, $userid) {
 943      global $DB;
 944      return $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid), 'optionid');
 945  }
 946  
 947  /**
 948   * Get my responses on a given choice.
 949   *
 950   * @param stdClass $choice Choice record
 951   * @return array of choice answers records
 952   * @since  Moodle 3.0
 953   */
 954  function choice_get_my_response($choice) {
 955      global $USER;
 956      return choice_get_user_response($choice, $USER->id);
 957  }
 958  
 959  
 960  /**
 961   * Get all the responses on a given choice.
 962   *
 963   * @param stdClass $choice Choice record
 964   * @return array of choice answers records
 965   * @since  Moodle 3.0
 966   */
 967  function choice_get_all_responses($choice) {
 968      global $DB;
 969      return $DB->get_records('choice_answers', array('choiceid' => $choice->id));
 970  }
 971  
 972  
 973  /**
 974   * Return true if we are allowd to view the choice results.
 975   *
 976   * @param stdClass $choice Choice record
 977   * @param rows|null $current my choice responses
 978   * @param bool|null $choiceopen if the choice is open
 979   * @return bool true if we can view the results, false otherwise.
 980   * @since  Moodle 3.0
 981   */
 982  function choice_can_view_results($choice, $current = null, $choiceopen = null) {
 983  
 984      if (is_null($choiceopen)) {
 985          $timenow = time();
 986  
 987          if ($choice->timeopen != 0 && $timenow < $choice->timeopen) {
 988              // If the choice is not available, we can't see the results.
 989              return false;
 990          }
 991  
 992          if ($choice->timeclose != 0 && $timenow > $choice->timeclose) {
 993              $choiceopen = false;
 994          } else {
 995              $choiceopen = true;
 996          }
 997      }
 998      if (empty($current)) {
 999          $current = choice_get_my_response($choice);
1000      }
1001  
1002      if ($choice->showresults == CHOICE_SHOWRESULTS_ALWAYS or
1003         ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_ANSWER and !empty($current)) or
1004         ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_CLOSE and !$choiceopen)) {
1005          return true;
1006      }
1007      return false;
1008  }
1009  
1010  /**
1011   * Mark the activity completed (if required) and trigger the course_module_viewed event.
1012   *
1013   * @param  stdClass $choice     choice object
1014   * @param  stdClass $course     course object
1015   * @param  stdClass $cm         course module object
1016   * @param  stdClass $context    context object
1017   * @since Moodle 3.0
1018   */
1019  function choice_view($choice, $course, $cm, $context) {
1020  
1021      // Trigger course_module_viewed event.
1022      $params = array(
1023          'context' => $context,
1024          'objectid' => $choice->id
1025      );
1026  
1027      $event = \mod_choice\event\course_module_viewed::create($params);
1028      $event->add_record_snapshot('course_modules', $cm);
1029      $event->add_record_snapshot('course', $course);
1030      $event->add_record_snapshot('choice', $choice);
1031      $event->trigger();
1032  
1033      // Completion.
1034      $completion = new completion_info($course);
1035      $completion->set_module_viewed($cm);
1036  }
1037  
1038  /**
1039   * Check if a choice is available for the current user.
1040   *
1041   * @param  stdClass  $choice            choice record
1042   * @return array                       status (available or not and possible warnings)
1043   */
1044  function choice_get_availability_status($choice) {
1045      $available = true;
1046      $warnings = array();
1047  
1048      $timenow = time();
1049  
1050      if (!empty($choice->timeopen) && ($choice->timeopen > $timenow)) {
1051          $available = false;
1052          $warnings['notopenyet'] = userdate($choice->timeopen);
1053      } else if (!empty($choice->timeclose) && ($timenow > $choice->timeclose)) {
1054          $available = false;
1055          $warnings['expired'] = userdate($choice->timeclose);
1056      }
1057      if (!$choice->allowupdate && choice_get_my_response($choice)) {
1058          $available = false;
1059          $warnings['choicesaved'] = '';
1060      }
1061  
1062      // Choice is available.
1063      return array($available, $warnings);
1064  }
1065  
1066  /**
1067   * This standard function will check all instances of this module
1068   * and make sure there are up-to-date events created for each of them.
1069   * If courseid = 0, then every choice event in the site is checked, else
1070   * only choice events belonging to the course specified are checked.
1071   * This function is used, in its new format, by restore_refresh_events()
1072   *
1073   * @param int $courseid
1074   * @param int|stdClass $instance Choice module instance or ID.
1075   * @param int|stdClass $cm Course module object or ID (not used in this module).
1076   * @return bool
1077   */
1078  function choice_refresh_events($courseid = 0, $instance = null, $cm = null) {
1079      global $DB, $CFG;
1080      require_once($CFG->dirroot.'/mod/choice/locallib.php');
1081  
1082      // If we have instance information then we can just update the one event instead of updating all events.
1083      if (isset($instance)) {
1084          if (!is_object($instance)) {
1085              $instance = $DB->get_record('choice', array('id' => $instance), '*', MUST_EXIST);
1086          }
1087          choice_set_events($instance);
1088          return true;
1089      }
1090  
1091      if ($courseid) {
1092          if (! $choices = $DB->get_records("choice", array("course" => $courseid))) {
1093              return true;
1094          }
1095      } else {
1096          if (! $choices = $DB->get_records("choice")) {
1097              return true;
1098          }
1099      }
1100  
1101      foreach ($choices as $choice) {
1102          choice_set_events($choice);
1103      }
1104      return true;
1105  }
1106  
1107  /**
1108   * Check if the module has any update that affects the current user since a given time.
1109   *
1110   * @param  cm_info $cm course module data
1111   * @param  int $from the time to check updates from
1112   * @param  array $filter  if we need to check only specific updates
1113   * @return stdClass an object with the different type of areas indicating if they were updated or not
1114   * @since Moodle 3.2
1115   */
1116  function choice_check_updates_since(cm_info $cm, $from, $filter = array()) {
1117      global $DB;
1118  
1119      $updates = new stdClass();
1120      $choice = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
1121      list($available, $warnings) = choice_get_availability_status($choice);
1122      if (!$available) {
1123          return $updates;
1124      }
1125  
1126      $updates = course_check_module_updates_since($cm, $from, array(), $filter);
1127  
1128      if (!choice_can_view_results($choice)) {
1129          return $updates;
1130      }
1131      // Check if there are new responses in the choice.
1132      $updates->answers = (object) array('updated' => false);
1133      $select = 'choiceid = :id AND timemodified > :since';
1134      $params = array('id' => $choice->id, 'since' => $from);
1135      $answers = $DB->get_records_select('choice_answers', $select, $params, '', 'id');
1136      if (!empty($answers)) {
1137          $updates->answers->updated = true;
1138          $updates->answers->itemids = array_keys($answers);
1139      }
1140  
1141      return $updates;
1142  }
1143  
1144  /**
1145   * This function receives a calendar event and returns the action associated with it, or null if there is none.
1146   *
1147   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1148   * is not displayed on the block.
1149   *
1150   * @param calendar_event $event
1151   * @param \core_calendar\action_factory $factory
1152   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1153   * @return \core_calendar\local\event\entities\action_interface|null
1154   */
1155  function mod_choice_core_calendar_provide_event_action(calendar_event $event,
1156                                                         \core_calendar\action_factory $factory,
1157                                                         int $userid = 0) {
1158      global $USER;
1159  
1160      if (!$userid) {
1161          $userid = $USER->id;
1162      }
1163  
1164      $cm = get_fast_modinfo($event->courseid, $userid)->instances['choice'][$event->instance];
1165  
1166      if (!$cm->uservisible) {
1167          // The module is not visible to the user for any reason.
1168          return null;
1169      }
1170  
1171      $completion = new \completion_info($cm->get_course());
1172  
1173      $completiondata = $completion->get_data($cm, false, $userid);
1174  
1175      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
1176          return null;
1177      }
1178  
1179      $now = time();
1180  
1181      if (!empty($cm->customdata['timeclose']) && $cm->customdata['timeclose'] < $now) {
1182          // The choice has closed so the user can no longer submit anything.
1183          return null;
1184      }
1185  
1186      // The choice is actionable if we don't have a start time or the start time is
1187      // in the past.
1188      $actionable = (empty($cm->customdata['timeopen']) || $cm->customdata['timeopen'] <= $now);
1189  
1190      if ($actionable && choice_get_user_response((object)['id' => $event->instance], $userid)) {
1191          // There is no action if the user has already submitted their choice.
1192          return null;
1193      }
1194  
1195      return $factory->create_instance(
1196          get_string('viewchoices', 'choice'),
1197          new \moodle_url('/mod/choice/view.php', array('id' => $cm->id)),
1198          1,
1199          $actionable
1200      );
1201  }
1202  
1203  /**
1204   * This function calculates the minimum and maximum cutoff values for the timestart of
1205   * the given event.
1206   *
1207   * It will return an array with two values, the first being the minimum cutoff value and
1208   * the second being the maximum cutoff value. Either or both values can be null, which
1209   * indicates there is no minimum or maximum, respectively.
1210   *
1211   * If a cutoff is required then the function must return an array containing the cutoff
1212   * timestamp and error string to display to the user if the cutoff value is violated.
1213   *
1214   * A minimum and maximum cutoff return value will look like:
1215   * [
1216   *     [1505704373, 'The date must be after this date'],
1217   *     [1506741172, 'The date must be before this date']
1218   * ]
1219   *
1220   * @param calendar_event $event The calendar event to get the time range for
1221   * @param stdClass $choice The module instance to get the range from
1222   */
1223  function mod_choice_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $choice) {
1224      $mindate = null;
1225      $maxdate = null;
1226  
1227      if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
1228          if (!empty($choice->timeclose)) {
1229              $maxdate = [
1230                  $choice->timeclose,
1231                  get_string('openafterclose', 'choice')
1232              ];
1233          }
1234      } else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
1235          if (!empty($choice->timeopen)) {
1236              $mindate = [
1237                  $choice->timeopen,
1238                  get_string('closebeforeopen', 'choice')
1239              ];
1240          }
1241      }
1242  
1243      return [$mindate, $maxdate];
1244  }
1245  
1246  /**
1247   * This function will update the choice module according to the
1248   * event that has been modified.
1249   *
1250   * It will set the timeopen or timeclose value of the choice instance
1251   * according to the type of event provided.
1252   *
1253   * @throws \moodle_exception
1254   * @param \calendar_event $event
1255   * @param stdClass $choice The module instance to get the range from
1256   */
1257  function mod_choice_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $choice) {
1258      global $DB;
1259  
1260      if (!in_array($event->eventtype, [CHOICE_EVENT_TYPE_OPEN, CHOICE_EVENT_TYPE_CLOSE])) {
1261          return;
1262      }
1263  
1264      $courseid = $event->courseid;
1265      $modulename = $event->modulename;
1266      $instanceid = $event->instance;
1267      $modified = false;
1268  
1269      // Something weird going on. The event is for a different module so
1270      // we should ignore it.
1271      if ($modulename != 'choice') {
1272          return;
1273      }
1274  
1275      if ($choice->id != $instanceid) {
1276          return;
1277      }
1278  
1279      $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1280      $context = context_module::instance($coursemodule->id);
1281  
1282      // The user does not have the capability to modify this activity.
1283      if (!has_capability('moodle/course:manageactivities', $context)) {
1284          return;
1285      }
1286  
1287      if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
1288          // If the event is for the choice activity opening then we should
1289          // set the start time of the choice activity to be the new start
1290          // time of the event.
1291          if ($choice->timeopen != $event->timestart) {
1292              $choice->timeopen = $event->timestart;
1293              $modified = true;
1294          }
1295      } else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
1296          // If the event is for the choice activity closing then we should
1297          // set the end time of the choice activity to be the new start
1298          // time of the event.
1299          if ($choice->timeclose != $event->timestart) {
1300              $choice->timeclose = $event->timestart;
1301              $modified = true;
1302          }
1303      }
1304  
1305      if ($modified) {
1306          $choice->timemodified = time();
1307          // Persist the instance changes.
1308          $DB->update_record('choice', $choice);
1309          $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
1310          $event->trigger();
1311      }
1312  }
1313  
1314  /**
1315   * Get icon mapping for font-awesome.
1316   */
1317  function mod_choice_get_fontawesome_icon_map() {
1318      return [
1319          'mod_choice:row' => 'fa-info',
1320          'mod_choice:column' => 'fa-columns',
1321      ];
1322  }
1323  
1324  /**
1325   * Add a get_coursemodule_info function in case any choice type wants to add 'extra' information
1326   * for the course (see resource).
1327   *
1328   * Given a course_module object, this function returns any "extra" information that may be needed
1329   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
1330   *
1331   * @param stdClass $coursemodule The coursemodule object (record).
1332   * @return cached_cm_info An object on information that the courses
1333   *                        will know about (most noticeably, an icon).
1334   */
1335  function choice_get_coursemodule_info($coursemodule) {
1336      global $DB;
1337  
1338      $dbparams = ['id' => $coursemodule->instance];
1339      $fields = 'id, name, intro, introformat, completionsubmit, timeopen, timeclose';
1340      if (!$choice = $DB->get_record('choice', $dbparams, $fields)) {
1341          return false;
1342      }
1343  
1344      $result = new cached_cm_info();
1345      $result->name = $choice->name;
1346  
1347      if ($coursemodule->showdescription) {
1348          // Convert intro to html. Do not filter cached version, filters run at display time.
1349          $result->content = format_module_intro('choice', $choice, $coursemodule->id, false);
1350      }
1351  
1352      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
1353      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
1354          $result->customdata['customcompletionrules']['completionsubmit'] = $choice->completionsubmit;
1355      }
1356      // Populate some other values that can be used in calendar or on dashboard.
1357      if ($choice->timeopen) {
1358          $result->customdata['timeopen'] = $choice->timeopen;
1359      }
1360      if ($choice->timeclose) {
1361          $result->customdata['timeclose'] = $choice->timeclose;
1362      }
1363  
1364      return $result;
1365  }
1366  
1367  /**
1368   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
1369   *
1370   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
1371   * @return array $descriptions the array of descriptions for the custom rules.
1372   */
1373  function mod_choice_get_completion_active_rule_descriptions($cm) {
1374      // Values will be present in cm_info, and we assume these are up to date.
1375      if (empty($cm->customdata['customcompletionrules'])
1376          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
1377          return [];
1378      }
1379  
1380      $descriptions = [];
1381      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
1382          switch ($key) {
1383              case 'completionsubmit':
1384                  if (!empty($val)) {
1385                      $descriptions[] = get_string('completionsubmit', 'choice');
1386                  }
1387                  break;
1388              default:
1389                  break;
1390          }
1391      }
1392      return $descriptions;
1393  }