PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/mod/choice/lib.php

http://github.com/moodle/moodle
PHP | 1133 lines | 643 code | 149 blank | 341 comment | 131 complexity | c451da1063dcd9a8cd73f2583b1cd812 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  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. * @package mod_choice
  18. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  19. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  20. */
  21. defined('MOODLE_INTERNAL') || die();
  22. /** @global int $CHOICE_COLUMN_HEIGHT */
  23. global $CHOICE_COLUMN_HEIGHT;
  24. $CHOICE_COLUMN_HEIGHT = 300;
  25. /** @global int $CHOICE_COLUMN_WIDTH */
  26. global $CHOICE_COLUMN_WIDTH;
  27. $CHOICE_COLUMN_WIDTH = 300;
  28. define('CHOICE_PUBLISH_ANONYMOUS', '0');
  29. define('CHOICE_PUBLISH_NAMES', '1');
  30. define('CHOICE_SHOWRESULTS_NOT', '0');
  31. define('CHOICE_SHOWRESULTS_AFTER_ANSWER', '1');
  32. define('CHOICE_SHOWRESULTS_AFTER_CLOSE', '2');
  33. define('CHOICE_SHOWRESULTS_ALWAYS', '3');
  34. define('CHOICE_DISPLAY_HORIZONTAL', '0');
  35. define('CHOICE_DISPLAY_VERTICAL', '1');
  36. /** @global array $CHOICE_PUBLISH */
  37. global $CHOICE_PUBLISH;
  38. $CHOICE_PUBLISH = array (CHOICE_PUBLISH_ANONYMOUS => get_string('publishanonymous', 'choice'),
  39. CHOICE_PUBLISH_NAMES => get_string('publishnames', 'choice'));
  40. /** @global array $CHOICE_SHOWRESULTS */
  41. global $CHOICE_SHOWRESULTS;
  42. $CHOICE_SHOWRESULTS = array (CHOICE_SHOWRESULTS_NOT => get_string('publishnot', 'choice'),
  43. CHOICE_SHOWRESULTS_AFTER_ANSWER => get_string('publishafteranswer', 'choice'),
  44. CHOICE_SHOWRESULTS_AFTER_CLOSE => get_string('publishafterclose', 'choice'),
  45. CHOICE_SHOWRESULTS_ALWAYS => get_string('publishalways', 'choice'));
  46. /** @global array $CHOICE_DISPLAY */
  47. global $CHOICE_DISPLAY;
  48. $CHOICE_DISPLAY = array (CHOICE_DISPLAY_HORIZONTAL => get_string('displayhorizontal', 'choice'),
  49. CHOICE_DISPLAY_VERTICAL => get_string('displayvertical','choice'));
  50. /// Standard functions /////////////////////////////////////////////////////////
  51. /**
  52. * @global object
  53. * @param object $course
  54. * @param object $user
  55. * @param object $mod
  56. * @param object $choice
  57. * @return object|null
  58. */
  59. function choice_user_outline($course, $user, $mod, $choice) {
  60. global $DB;
  61. if ($answer = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $user->id))) {
  62. $result = new stdClass();
  63. $result->info = "'".format_string(choice_get_option_text($choice, $answer->optionid))."'";
  64. $result->time = $answer->timemodified;
  65. return $result;
  66. }
  67. return NULL;
  68. }
  69. /**
  70. * Callback for the "Complete" report - prints the activity summary for the given user
  71. *
  72. * @param object $course
  73. * @param object $user
  74. * @param object $mod
  75. * @param object $choice
  76. */
  77. function choice_user_complete($course, $user, $mod, $choice) {
  78. global $DB;
  79. if ($answers = $DB->get_records('choice_answers', array("choiceid" => $choice->id, "userid" => $user->id))) {
  80. $info = [];
  81. foreach ($answers as $answer) {
  82. $info[] = "'" . format_string(choice_get_option_text($choice, $answer->optionid)) . "'";
  83. }
  84. core_collator::asort($info);
  85. echo get_string("answered", "choice") . ": ". join(', ', $info) . ". " .
  86. get_string("updated", '', userdate($answer->timemodified));
  87. } else {
  88. print_string("notanswered", "choice");
  89. }
  90. }
  91. /**
  92. * Given an object containing all the necessary data,
  93. * (defined by the form in mod_form.php) this function
  94. * will create a new instance and return the id number
  95. * of the new instance.
  96. *
  97. * @global object
  98. * @param object $choice
  99. * @return int
  100. */
  101. function choice_add_instance($choice) {
  102. global $DB, $CFG;
  103. require_once($CFG->dirroot.'/mod/choice/locallib.php');
  104. $choice->timemodified = time();
  105. //insert answers
  106. $choice->id = $DB->insert_record("choice", $choice);
  107. foreach ($choice->option as $key => $value) {
  108. $value = trim($value);
  109. if (isset($value) && $value <> '') {
  110. $option = new stdClass();
  111. $option->text = $value;
  112. $option->choiceid = $choice->id;
  113. if (isset($choice->limit[$key])) {
  114. $option->maxanswers = $choice->limit[$key];
  115. }
  116. $option->timemodified = time();
  117. $DB->insert_record("choice_options", $option);
  118. }
  119. }
  120. // Add calendar events if necessary.
  121. choice_set_events($choice);
  122. return $choice->id;
  123. }
  124. /**
  125. * Given an object containing all the necessary data,
  126. * (defined by the form in mod_form.php) this function
  127. * will update an existing instance with new data.
  128. *
  129. * @global object
  130. * @param object $choice
  131. * @return bool
  132. */
  133. function choice_update_instance($choice) {
  134. global $DB, $CFG;
  135. require_once($CFG->dirroot.'/mod/choice/locallib.php');
  136. $choice->id = $choice->instance;
  137. $choice->timemodified = time();
  138. //update, delete or insert answers
  139. foreach ($choice->option as $key => $value) {
  140. $value = trim($value);
  141. $option = new stdClass();
  142. $option->text = $value;
  143. $option->choiceid = $choice->id;
  144. if (isset($choice->limit[$key])) {
  145. $option->maxanswers = $choice->limit[$key];
  146. }
  147. $option->timemodified = time();
  148. if (isset($choice->optionid[$key]) && !empty($choice->optionid[$key])){//existing choice record
  149. $option->id=$choice->optionid[$key];
  150. if (isset($value) && $value <> '') {
  151. $DB->update_record("choice_options", $option);
  152. } else {
  153. // Remove the empty (unused) option.
  154. $DB->delete_records("choice_options", array("id" => $option->id));
  155. // Delete any answers associated with this option.
  156. $DB->delete_records("choice_answers", array("choiceid" => $choice->id, "optionid" => $option->id));
  157. }
  158. } else {
  159. if (isset($value) && $value <> '') {
  160. $DB->insert_record("choice_options", $option);
  161. }
  162. }
  163. }
  164. // Add calendar events if necessary.
  165. choice_set_events($choice);
  166. return $DB->update_record('choice', $choice);
  167. }
  168. /**
  169. * @global object
  170. * @param object $choice
  171. * @param object $user
  172. * @param object $coursemodule
  173. * @param array $allresponses
  174. * @return array
  175. */
  176. function choice_prepare_options($choice, $user, $coursemodule, $allresponses) {
  177. global $DB;
  178. $cdisplay = array('options'=>array());
  179. $cdisplay['limitanswers'] = true;
  180. $context = context_module::instance($coursemodule->id);
  181. foreach ($choice->option as $optionid => $text) {
  182. if (isset($text)) { //make sure there are no dud entries in the db with blank text values.
  183. $option = new stdClass;
  184. $option->attributes = new stdClass;
  185. $option->attributes->value = $optionid;
  186. $option->text = format_string($text);
  187. $option->maxanswers = $choice->maxanswers[$optionid];
  188. $option->displaylayout = $choice->display;
  189. if (isset($allresponses[$optionid])) {
  190. $option->countanswers = count($allresponses[$optionid]);
  191. } else {
  192. $option->countanswers = 0;
  193. }
  194. if ($DB->record_exists('choice_answers', array('choiceid' => $choice->id, 'userid' => $user->id, 'optionid' => $optionid))) {
  195. $option->attributes->checked = true;
  196. }
  197. if ( $choice->limitanswers && ($option->countanswers >= $option->maxanswers) && empty($option->attributes->checked)) {
  198. $option->attributes->disabled = true;
  199. }
  200. $cdisplay['options'][] = $option;
  201. }
  202. }
  203. $cdisplay['hascapability'] = is_enrolled($context, NULL, 'mod/choice:choose'); //only enrolled users are allowed to make a choice
  204. if ($choice->allowupdate && $DB->record_exists('choice_answers', array('choiceid'=> $choice->id, 'userid'=> $user->id))) {
  205. $cdisplay['allowupdate'] = true;
  206. }
  207. if ($choice->showpreview && $choice->timeopen > time()) {
  208. $cdisplay['previewonly'] = true;
  209. }
  210. return $cdisplay;
  211. }
  212. /**
  213. * Modifies responses of other users adding the option $newoptionid to them
  214. *
  215. * @param array $userids list of users to add option to (must be users without any answers yet)
  216. * @param array $answerids list of existing attempt ids of users (will be either appended or
  217. * substituted with the newoptionid, depending on $choice->allowmultiple)
  218. * @param int $newoptionid
  219. * @param stdClass $choice choice object, result of {@link choice_get_choice()}
  220. * @param stdClass $cm
  221. * @param stdClass $course
  222. */
  223. function choice_modify_responses($userids, $answerids, $newoptionid, $choice, $cm, $course) {
  224. // Get all existing responses and the list of non-respondents.
  225. $groupmode = groups_get_activity_groupmode($cm);
  226. $onlyactive = $choice->includeinactive ? false : true;
  227. $allresponses = choice_get_response_data($choice, $cm, $groupmode, $onlyactive);
  228. // Check that the option value is valid.
  229. if (!$newoptionid || !isset($choice->option[$newoptionid])) {
  230. return;
  231. }
  232. // First add responses for users who did not make any choice yet.
  233. foreach ($userids as $userid) {
  234. if (isset($allresponses[0][$userid])) {
  235. choice_user_submit_response($newoptionid, $choice, $userid, $course, $cm);
  236. }
  237. }
  238. // Create the list of all options already selected by each user.
  239. $optionsbyuser = []; // Mapping userid=>array of chosen choice options.
  240. $usersbyanswer = []; // Mapping answerid=>userid (which answer belongs to each user).
  241. foreach ($allresponses as $optionid => $responses) {
  242. if ($optionid > 0) {
  243. foreach ($responses as $userid => $userresponse) {
  244. $optionsbyuser += [$userid => []];
  245. $optionsbyuser[$userid][] = $optionid;
  246. $usersbyanswer[$userresponse->answerid] = $userid;
  247. }
  248. }
  249. }
  250. // Go through the list of submitted attemptids and find which users answers need to be updated.
  251. foreach ($answerids as $answerid) {
  252. if (isset($usersbyanswer[$answerid])) {
  253. $userid = $usersbyanswer[$answerid];
  254. if (!in_array($newoptionid, $optionsbyuser[$userid])) {
  255. $options = $choice->allowmultiple ?
  256. array_merge($optionsbyuser[$userid], [$newoptionid]) : $newoptionid;
  257. choice_user_submit_response($options, $choice, $userid, $course, $cm);
  258. }
  259. }
  260. }
  261. }
  262. /**
  263. * Process user submitted answers for a choice,
  264. * and either updating them or saving new answers.
  265. *
  266. * @param int $formanswer users submitted answers.
  267. * @param object $choice the selected choice.
  268. * @param int $userid user identifier.
  269. * @param object $course current course.
  270. * @param object $cm course context.
  271. * @return void
  272. */
  273. function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm) {
  274. global $DB, $CFG, $USER;
  275. require_once($CFG->libdir.'/completionlib.php');
  276. $continueurl = new moodle_url('/mod/choice/view.php', array('id' => $cm->id));
  277. if (empty($formanswer)) {
  278. print_error('atleastoneoption', 'choice', $continueurl);
  279. }
  280. if (is_array($formanswer)) {
  281. if (!$choice->allowmultiple) {
  282. print_error('multiplenotallowederror', 'choice', $continueurl);
  283. }
  284. $formanswers = $formanswer;
  285. } else {
  286. $formanswers = array($formanswer);
  287. }
  288. $options = $DB->get_records('choice_options', array('choiceid' => $choice->id), '', 'id');
  289. foreach ($formanswers as $key => $val) {
  290. if (!isset($options[$val])) {
  291. print_error('cannotsubmit', 'choice', $continueurl);
  292. }
  293. }
  294. // Start lock to prevent synchronous access to the same data
  295. // before it's updated, if using limits.
  296. if ($choice->limitanswers) {
  297. $timeout = 10;
  298. $locktype = 'mod_choice_choice_user_submit_response';
  299. // Limiting access to this choice.
  300. $resouce = 'choiceid:' . $choice->id;
  301. $lockfactory = \core\lock\lock_config::get_lock_factory($locktype);
  302. // Opening the lock.
  303. $choicelock = $lockfactory->get_lock($resouce, $timeout);
  304. if (!$choicelock) {
  305. print_error('cannotsubmit', 'choice', $continueurl);
  306. }
  307. }
  308. $current = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid));
  309. $context = context_module::instance($cm->id);
  310. $choicesexceeded = false;
  311. $countanswers = array();
  312. foreach ($formanswers as $val) {
  313. $countanswers[$val] = 0;
  314. }
  315. if($choice->limitanswers) {
  316. // Find out whether groups are being used and enabled
  317. if (groups_get_activity_groupmode($cm) > 0) {
  318. $currentgroup = groups_get_activity_group($cm);
  319. } else {
  320. $currentgroup = 0;
  321. }
  322. list ($insql, $params) = $DB->get_in_or_equal($formanswers, SQL_PARAMS_NAMED);
  323. if($currentgroup) {
  324. // If groups are being used, retrieve responses only for users in
  325. // current group
  326. global $CFG;
  327. $params['groupid'] = $currentgroup;
  328. $sql = "SELECT ca.*
  329. FROM {choice_answers} ca
  330. INNER JOIN {groups_members} gm ON ca.userid=gm.userid
  331. WHERE optionid $insql
  332. AND gm.groupid= :groupid";
  333. } else {
  334. // Groups are not used, retrieve all answers for this option ID
  335. $sql = "SELECT ca.*
  336. FROM {choice_answers} ca
  337. WHERE optionid $insql";
  338. }
  339. $answers = $DB->get_records_sql($sql, $params);
  340. if ($answers) {
  341. foreach ($answers as $a) { //only return enrolled users.
  342. if (is_enrolled($context, $a->userid, 'mod/choice:choose')) {
  343. $countanswers[$a->optionid]++;
  344. }
  345. }
  346. }
  347. foreach ($countanswers as $opt => $count) {
  348. if ($count >= $choice->maxanswers[$opt]) {
  349. $choicesexceeded = true;
  350. break;
  351. }
  352. }
  353. }
  354. // Check the user hasn't exceeded the maximum selections for the choice(s) they have selected.
  355. $answersnapshots = array();
  356. $deletedanswersnapshots = array();
  357. if (!($choice->limitanswers && $choicesexceeded)) {
  358. if ($current) {
  359. // Update an existing answer.
  360. $existingchoices = array();
  361. foreach ($current as $c) {
  362. if (in_array($c->optionid, $formanswers)) {
  363. $existingchoices[] = $c->optionid;
  364. $DB->set_field('choice_answers', 'timemodified', time(), array('id' => $c->id));
  365. } else {
  366. $deletedanswersnapshots[] = $c;
  367. $DB->delete_records('choice_answers', array('id' => $c->id));
  368. }
  369. }
  370. // Add new ones.
  371. foreach ($formanswers as $f) {
  372. if (!in_array($f, $existingchoices)) {
  373. $newanswer = new stdClass();
  374. $newanswer->optionid = $f;
  375. $newanswer->choiceid = $choice->id;
  376. $newanswer->userid = $userid;
  377. $newanswer->timemodified = time();
  378. $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
  379. $answersnapshots[] = $newanswer;
  380. }
  381. }
  382. } else {
  383. // Add new answer.
  384. foreach ($formanswers as $answer) {
  385. $newanswer = new stdClass();
  386. $newanswer->choiceid = $choice->id;
  387. $newanswer->userid = $userid;
  388. $newanswer->optionid = $answer;
  389. $newanswer->timemodified = time();
  390. $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
  391. $answersnapshots[] = $newanswer;
  392. }
  393. // Update completion state
  394. $completion = new completion_info($course);
  395. if ($completion->is_enabled($cm) && $choice->completionsubmit) {
  396. $completion->update_state($cm, COMPLETION_COMPLETE);
  397. }
  398. }
  399. } else {
  400. // Check to see if current choice already selected - if not display error.
  401. $currentids = array_keys($current);
  402. if (array_diff($currentids, $formanswers) || array_diff($formanswers, $currentids) ) {
  403. // Release lock before error.
  404. $choicelock->release();
  405. print_error('choicefull', 'choice', $continueurl);
  406. }
  407. }
  408. // Release lock.
  409. if (isset($choicelock)) {
  410. $choicelock->release();
  411. }
  412. // Trigger events.
  413. foreach ($deletedanswersnapshots as $answer) {
  414. \mod_choice\event\answer_deleted::create_from_object($answer, $choice, $cm, $course)->trigger();
  415. }
  416. foreach ($answersnapshots as $answer) {
  417. \mod_choice\event\answer_created::create_from_object($answer, $choice, $cm, $course)->trigger();
  418. }
  419. }
  420. /**
  421. * @param array $user
  422. * @param object $cm
  423. * @return void Output is echo'd
  424. */
  425. function choice_show_reportlink($user, $cm) {
  426. $userschosen = array();
  427. foreach($user as $optionid => $userlist) {
  428. if ($optionid) {
  429. $userschosen = array_merge($userschosen, array_keys($userlist));
  430. }
  431. }
  432. $responsecount = count(array_unique($userschosen));
  433. echo '<div class="reportlink">';
  434. echo "<a href=\"report.php?id=$cm->id\">".get_string("viewallresponses", "choice", $responsecount)."</a>";
  435. echo '</div>';
  436. }
  437. /**
  438. * @global object
  439. * @param object $choice
  440. * @param object $course
  441. * @param object $coursemodule
  442. * @param array $allresponses
  443. * * @param bool $allresponses
  444. * @return object
  445. */
  446. function prepare_choice_show_results($choice, $course, $cm, $allresponses) {
  447. global $OUTPUT;
  448. $display = clone($choice);
  449. $display->coursemoduleid = $cm->id;
  450. $display->courseid = $course->id;
  451. if (!empty($choice->showunanswered)) {
  452. $choice->option[0] = get_string('notanswered', 'choice');
  453. $choice->maxanswers[0] = 0;
  454. }
  455. // Remove from the list of non-respondents the users who do not have access to this activity.
  456. if (!empty($display->showunanswered) && $allresponses[0]) {
  457. $info = new \core_availability\info_module(cm_info::create($cm));
  458. $allresponses[0] = $info->filter_user_list($allresponses[0]);
  459. }
  460. //overwrite options value;
  461. $display->options = array();
  462. $allusers = [];
  463. foreach ($choice->option as $optionid => $optiontext) {
  464. $display->options[$optionid] = new stdClass;
  465. $display->options[$optionid]->text = $optiontext;
  466. $display->options[$optionid]->maxanswer = $choice->maxanswers[$optionid];
  467. if (array_key_exists($optionid, $allresponses)) {
  468. $display->options[$optionid]->user = $allresponses[$optionid];
  469. $allusers = array_merge($allusers, array_keys($allresponses[$optionid]));
  470. }
  471. }
  472. unset($display->option);
  473. unset($display->maxanswers);
  474. $display->numberofuser = count(array_unique($allusers));
  475. $context = context_module::instance($cm->id);
  476. $display->viewresponsecapability = has_capability('mod/choice:readresponses', $context);
  477. $display->deleterepsonsecapability = has_capability('mod/choice:deleteresponses',$context);
  478. $display->fullnamecapability = has_capability('moodle/site:viewfullnames', $context);
  479. if (empty($allresponses)) {
  480. echo $OUTPUT->heading(get_string("nousersyet"), 3, null);
  481. return false;
  482. }
  483. return $display;
  484. }
  485. /**
  486. * @global object
  487. * @param array $attemptids
  488. * @param object $choice Choice main table row
  489. * @param object $cm Course-module object
  490. * @param object $course Course object
  491. * @return bool
  492. */
  493. function choice_delete_responses($attemptids, $choice, $cm, $course) {
  494. global $DB, $CFG, $USER;
  495. require_once($CFG->libdir.'/completionlib.php');
  496. if(!is_array($attemptids) || empty($attemptids)) {
  497. return false;
  498. }
  499. foreach($attemptids as $num => $attemptid) {
  500. if(empty($attemptid)) {
  501. unset($attemptids[$num]);
  502. }
  503. }
  504. $completion = new completion_info($course);
  505. foreach($attemptids as $attemptid) {
  506. if ($todelete = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid))) {
  507. // Trigger the event answer deleted.
  508. \mod_choice\event\answer_deleted::create_from_object($todelete, $choice, $cm, $course)->trigger();
  509. $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid));
  510. }
  511. }
  512. // Update completion state.
  513. if ($completion->is_enabled($cm) && $choice->completionsubmit) {
  514. $completion->update_state($cm, COMPLETION_INCOMPLETE);
  515. }
  516. return true;
  517. }
  518. /**
  519. * Given an ID of an instance of this module,
  520. * this function will permanently delete the instance
  521. * and any data that depends on it.
  522. *
  523. * @global object
  524. * @param int $id
  525. * @return bool
  526. */
  527. function choice_delete_instance($id) {
  528. global $DB;
  529. if (! $choice = $DB->get_record("choice", array("id"=>"$id"))) {
  530. return false;
  531. }
  532. $result = true;
  533. if (! $DB->delete_records("choice_answers", array("choiceid"=>"$choice->id"))) {
  534. $result = false;
  535. }
  536. if (! $DB->delete_records("choice_options", array("choiceid"=>"$choice->id"))) {
  537. $result = false;
  538. }
  539. if (! $DB->delete_records("choice", array("id"=>"$choice->id"))) {
  540. $result = false;
  541. }
  542. // Remove old calendar events.
  543. if (! $DB->delete_records('event', array('modulename' => 'choice', 'instance' => $choice->id))) {
  544. $result = false;
  545. }
  546. return $result;
  547. }
  548. /**
  549. * Returns text string which is the answer that matches the id
  550. *
  551. * @global object
  552. * @param object $choice
  553. * @param int $id
  554. * @return string
  555. */
  556. function choice_get_option_text($choice, $id) {
  557. global $DB;
  558. if ($result = $DB->get_record("choice_options", array("id" => $id))) {
  559. return $result->text;
  560. } else {
  561. return get_string("notanswered", "choice");
  562. }
  563. }
  564. /**
  565. * Gets a full choice record
  566. *
  567. * @global object
  568. * @param int $choiceid
  569. * @return object|bool The choice or false
  570. */
  571. function choice_get_choice($choiceid) {
  572. global $DB;
  573. if ($choice = $DB->get_record("choice", array("id" => $choiceid))) {
  574. if ($options = $DB->get_records("choice_options", array("choiceid" => $choiceid), "id")) {
  575. foreach ($options as $option) {
  576. $choice->option[$option->id] = $option->text;
  577. $choice->maxanswers[$option->id] = $option->maxanswers;
  578. }
  579. return $choice;
  580. }
  581. }
  582. return false;
  583. }
  584. /**
  585. * List the actions that correspond to a view of this module.
  586. * This is used by the participation report.
  587. *
  588. * Note: This is not used by new logging system. Event with
  589. * crud = 'r' and edulevel = LEVEL_PARTICIPATING will
  590. * be considered as view action.
  591. *
  592. * @return array
  593. */
  594. function choice_get_view_actions() {
  595. return array('view','view all','report');
  596. }
  597. /**
  598. * List the actions that correspond to a post of this module.
  599. * This is used by the participation report.
  600. *
  601. * Note: This is not used by new logging system. Event with
  602. * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
  603. * will be considered as post action.
  604. *
  605. * @return array
  606. */
  607. function choice_get_post_actions() {
  608. return array('choose','choose again');
  609. }
  610. /**
  611. * Implementation of the function for printing the form elements that control
  612. * whether the course reset functionality affects the choice.
  613. *
  614. * @param object $mform form passed by reference
  615. */
  616. function choice_reset_course_form_definition(&$mform) {
  617. $mform->addElement('header', 'choiceheader', get_string('modulenameplural', 'choice'));
  618. $mform->addElement('advcheckbox', 'reset_choice', get_string('removeresponses','choice'));
  619. }
  620. /**
  621. * Course reset form defaults.
  622. *
  623. * @return array
  624. */
  625. function choice_reset_course_form_defaults($course) {
  626. return array('reset_choice'=>1);
  627. }
  628. /**
  629. * Actual implementation of the reset course functionality, delete all the
  630. * choice responses for course $data->courseid.
  631. *
  632. * @global object
  633. * @global object
  634. * @param object $data the data submitted from the reset course.
  635. * @return array status array
  636. */
  637. function choice_reset_userdata($data) {
  638. global $CFG, $DB;
  639. $componentstr = get_string('modulenameplural', 'choice');
  640. $status = array();
  641. if (!empty($data->reset_choice)) {
  642. $choicessql = "SELECT ch.id
  643. FROM {choice} ch
  644. WHERE ch.course=?";
  645. $DB->delete_records_select('choice_answers', "choiceid IN ($choicessql)", array($data->courseid));
  646. $status[] = array('component'=>$componentstr, 'item'=>get_string('removeresponses', 'choice'), 'error'=>false);
  647. }
  648. /// updating dates - shift may be negative too
  649. if ($data->timeshift) {
  650. shift_course_mod_dates('choice', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid);
  651. $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
  652. }
  653. return $status;
  654. }
  655. /**
  656. * @global object
  657. * @global object
  658. * @global object
  659. * @uses CONTEXT_MODULE
  660. * @param object $choice
  661. * @param object $cm
  662. * @param int $groupmode
  663. * @param bool $onlyactive Whether to get response data for active users only.
  664. * @return array
  665. */
  666. function choice_get_response_data($choice, $cm, $groupmode, $onlyactive) {
  667. global $CFG, $USER, $DB;
  668. $context = context_module::instance($cm->id);
  669. /// Get the current group
  670. if ($groupmode > 0) {
  671. $currentgroup = groups_get_activity_group($cm);
  672. } else {
  673. $currentgroup = 0;
  674. }
  675. /// Initialise the returned array, which is a matrix: $allresponses[responseid][userid] = responseobject
  676. $allresponses = array();
  677. /// First get all the users who have access here
  678. /// To start with we assume they are all "unanswered" then move them later
  679. $allresponses[0] = get_enrolled_users($context, 'mod/choice:choose', $currentgroup,
  680. user_picture::fields('u', array('idnumber')), null, 0, 0, $onlyactive);
  681. /// Get all the recorded responses for this choice
  682. $rawresponses = $DB->get_records('choice_answers', array('choiceid' => $choice->id));
  683. /// Use the responses to move users into the correct column
  684. if ($rawresponses) {
  685. $answeredusers = array();
  686. foreach ($rawresponses as $response) {
  687. if (isset($allresponses[0][$response->userid])) { // This person is enrolled and in correct group
  688. $allresponses[0][$response->userid]->timemodified = $response->timemodified;
  689. $allresponses[$response->optionid][$response->userid] = clone($allresponses[0][$response->userid]);
  690. $allresponses[$response->optionid][$response->userid]->answerid = $response->id;
  691. $answeredusers[] = $response->userid;
  692. }
  693. }
  694. foreach ($answeredusers as $answereduser) {
  695. unset($allresponses[0][$answereduser]);
  696. }
  697. }
  698. return $allresponses;
  699. }
  700. /**
  701. * Returns all other caps used in module
  702. *
  703. * @return array
  704. */
  705. function choice_get_extra_capabilities() {
  706. return array('moodle/site:accessallgroups');
  707. }
  708. /**
  709. * @uses FEATURE_GROUPS
  710. * @uses FEATURE_GROUPINGS
  711. * @uses FEATURE_MOD_INTRO
  712. * @uses FEATURE_COMPLETION_TRACKS_VIEWS
  713. * @uses FEATURE_GRADE_HAS_GRADE
  714. * @uses FEATURE_GRADE_OUTCOMES
  715. * @param string $feature FEATURE_xx constant for requested feature
  716. * @return mixed True if module supports feature, null if doesn't know
  717. */
  718. function choice_supports($feature) {
  719. switch($feature) {
  720. case FEATURE_GROUPS: return true;
  721. case FEATURE_GROUPINGS: return true;
  722. case FEATURE_MOD_INTRO: return true;
  723. case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
  724. case FEATURE_COMPLETION_HAS_RULES: return true;
  725. case FEATURE_GRADE_HAS_GRADE: return false;
  726. case FEATURE_GRADE_OUTCOMES: return false;
  727. case FEATURE_BACKUP_MOODLE2: return true;
  728. case FEATURE_SHOW_DESCRIPTION: return true;
  729. default: return null;
  730. }
  731. }
  732. /**
  733. * Adds module specific settings to the settings block
  734. *
  735. * @param settings_navigation $settings The settings navigation object
  736. * @param navigation_node $choicenode The node to add module settings to
  737. */
  738. function choice_extend_settings_navigation(settings_navigation $settings, navigation_node $choicenode) {
  739. global $PAGE;
  740. if (has_capability('mod/choice:readresponses', $PAGE->cm->context)) {
  741. $groupmode = groups_get_activity_groupmode($PAGE->cm);
  742. if ($groupmode) {
  743. groups_get_activity_group($PAGE->cm, true);
  744. }
  745. $choice = choice_get_choice($PAGE->cm->instance);
  746. // Check if we want to include responses from inactive users.
  747. $onlyactive = $choice->includeinactive ? false : true;
  748. // Big function, approx 6 SQL calls per user.
  749. $allresponses = choice_get_response_data($choice, $PAGE->cm, $groupmode, $onlyactive);
  750. $allusers = [];
  751. foreach($allresponses as $optionid => $userlist) {
  752. if ($optionid) {
  753. $allusers = array_merge($allusers, array_keys($userlist));
  754. }
  755. }
  756. $responsecount = count(array_unique($allusers));
  757. $choicenode->add(get_string("viewallresponses", "choice", $responsecount), new moodle_url('/mod/choice/report.php', array('id'=>$PAGE->cm->id)));
  758. }
  759. }
  760. /**
  761. * Obtains the automatic completion state for this choice based on any conditions
  762. * in forum settings.
  763. *
  764. * @param object $course Course
  765. * @param object $cm Course-module
  766. * @param int $userid User ID
  767. * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
  768. * @return bool True if completed, false if not, $type if conditions not set.
  769. */
  770. function choice_get_completion_state($course, $cm, $userid, $type) {
  771. global $CFG,$DB;
  772. // Get choice details
  773. $choice = $DB->get_record('choice', array('id'=>$cm->instance), '*',
  774. MUST_EXIST);
  775. // If completion option is enabled, evaluate it and return true/false
  776. if($choice->completionsubmit) {
  777. return $DB->record_exists('choice_answers', array(
  778. 'choiceid'=>$choice->id, 'userid'=>$userid));
  779. } else {
  780. // Completion option is not enabled so just return $type
  781. return $type;
  782. }
  783. }
  784. /**
  785. * Return a list of page types
  786. * @param string $pagetype current page type
  787. * @param stdClass $parentcontext Block's parent context
  788. * @param stdClass $currentcontext Current context of block
  789. */
  790. function choice_page_type_list($pagetype, $parentcontext, $currentcontext) {
  791. $module_pagetype = array('mod-choice-*'=>get_string('page-mod-choice-x', 'choice'));
  792. return $module_pagetype;
  793. }
  794. /**
  795. * Prints choice summaries on MyMoodle Page
  796. *
  797. * Prints choice name, due date and attempt information on
  798. * choice activities that have a deadline that has not already passed
  799. * and it is available for completing.
  800. * @uses CONTEXT_MODULE
  801. * @param array $courses An array of course objects to get choice instances from.
  802. * @param array $htmlarray Store overview output array( course ID => 'choice' => HTML output )
  803. */
  804. function choice_print_overview($courses, &$htmlarray) {
  805. global $USER, $DB, $OUTPUT;
  806. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  807. return;
  808. }
  809. if (!$choices = get_all_instances_in_courses('choice', $courses)) {
  810. return;
  811. }
  812. $now = time();
  813. foreach ($choices as $choice) {
  814. if ($choice->timeclose != 0 // If this choice is scheduled.
  815. and $choice->timeclose >= $now // And the deadline has not passed.
  816. and ($choice->timeopen == 0 or $choice->timeopen <= $now)) { // And the choice is available.
  817. // Visibility.
  818. $class = (!$choice->visible) ? 'dimmed' : '';
  819. // Link to activity.
  820. $url = new moodle_url('/mod/choice/view.php', array('id' => $choice->coursemodule));
  821. $url = html_writer::link($url, format_string($choice->name), array('class' => $class));
  822. $str = $OUTPUT->box(get_string('choiceactivityname', 'choice', $url), 'name');
  823. // Deadline.
  824. $str .= $OUTPUT->box(get_string('choicecloseson', 'choice', userdate($choice->timeclose)), 'info');
  825. // Display relevant info based on permissions.
  826. if (has_capability('mod/choice:readresponses', context_module::instance($choice->coursemodule))) {
  827. $attempts = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {choice_answers} WHERE choiceid = ?',
  828. [$choice->id]);
  829. $url = new moodle_url('/mod/choice/report.php', ['id' => $choice->coursemodule]);
  830. $str .= $OUTPUT->box(html_writer::link($url, get_string('viewallresponses', 'choice', $attempts)), 'info');
  831. } else if (has_capability('mod/choice:choose', context_module::instance($choice->coursemodule))) {
  832. // See if the user has submitted anything.
  833. $answers = $DB->count_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
  834. if ($answers > 0) {
  835. // User has already selected an answer, nothing to show.
  836. $str = '';
  837. } else {
  838. // User has not made a selection yet.
  839. $str .= $OUTPUT->box(get_string('notanswered', 'choice'), 'info');
  840. }
  841. } else {
  842. // Does not have permission to do anything on this choice activity.
  843. $str = '';
  844. }
  845. // Make sure we have something to display.
  846. if (!empty($str)) {
  847. // Generate the containing div.
  848. $str = $OUTPUT->box($str, 'choice overview');
  849. if (empty($htmlarray[$choice->course]['choice'])) {
  850. $htmlarray[$choice->course]['choice'] = $str;
  851. } else {
  852. $htmlarray[$choice->course]['choice'] .= $str;
  853. }
  854. }
  855. }
  856. }
  857. return;
  858. }
  859. /**
  860. * Get my responses on a given choice.
  861. *
  862. * @param stdClass $choice Choice record
  863. * @return array of choice answers records
  864. * @since Moodle 3.0
  865. */
  866. function choice_get_my_response($choice) {
  867. global $DB, $USER;
  868. return $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
  869. }
  870. /**
  871. * Get all the responses on a given choice.
  872. *
  873. * @param stdClass $choice Choice record
  874. * @return array of choice answers records
  875. * @since Moodle 3.0
  876. */
  877. function choice_get_all_responses($choice) {
  878. global $DB;
  879. return $DB->get_records('choice_answers', array('choiceid' => $choice->id));
  880. }
  881. /**
  882. * Return true if we are allowd to view the choice results.
  883. *
  884. * @param stdClass $choice Choice record
  885. * @param rows|null $current my choice responses
  886. * @param bool|null $choiceopen if the choice is open
  887. * @return bool true if we can view the results, false otherwise.
  888. * @since Moodle 3.0
  889. */
  890. function choice_can_view_results($choice, $current = null, $choiceopen = null) {
  891. if (is_null($choiceopen)) {
  892. $timenow = time();
  893. if ($choice->timeclose != 0 && $timenow > $choice->timeclose) {
  894. $choiceopen = false;
  895. } else {
  896. $choiceopen = true;
  897. }
  898. }
  899. if (empty($current)) {
  900. $current = choice_get_my_response($choice);
  901. }
  902. if ($choice->showresults == CHOICE_SHOWRESULTS_ALWAYS or
  903. ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_ANSWER and !empty($current)) or
  904. ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_CLOSE and !$choiceopen)) {
  905. return true;
  906. }
  907. return false;
  908. }
  909. /**
  910. * Mark the activity completed (if required) and trigger the course_module_viewed event.
  911. *
  912. * @param stdClass $choice choice object
  913. * @param stdClass $course course object
  914. * @param stdClass $cm course module object
  915. * @param stdClass $context context object
  916. * @since Moodle 3.0
  917. */
  918. function choice_view($choice, $course, $cm, $context) {
  919. // Trigger course_module_viewed event.
  920. $params = array(
  921. 'context' => $context,
  922. 'objectid' => $choice->id
  923. );
  924. $event = \mod_choice\event\course_module_viewed::create($params);
  925. $event->add_record_snapshot('course_modules', $cm);
  926. $event->add_record_snapshot('course', $course);
  927. $event->add_record_snapshot('choice', $choice);
  928. $event->trigger();
  929. // Completion.
  930. $completion = new completion_info($course);
  931. $completion->set_module_viewed($cm);
  932. }
  933. /**
  934. * Check if a choice is available for the current user.
  935. *
  936. * @param stdClass $choice choice record
  937. * @return array status (available or not and possible warnings)
  938. */
  939. function choice_get_availability_status($choice) {
  940. $available = true;
  941. $warnings = array();
  942. $timenow = time();
  943. if (!empty($choice->timeopen) && ($choice->timeopen > $timenow)) {
  944. $available = false;
  945. $warnings['notopenyet'] = userdate($choice->timeopen);
  946. } else if (!empty($choice->timeclose) && ($timenow > $choice->timeclose)) {
  947. $available = false;
  948. $warnings['expired'] = userdate($choice->timeclose);
  949. }
  950. if (!$choice->allowupdate && choice_get_my_response($choice)) {
  951. $available = false;
  952. $warnings['choicesaved'] = '';
  953. }
  954. // Choice is available.
  955. return array($available, $warnings);
  956. }
  957. /**
  958. * This standard function will check all instances of this module
  959. * and make sure there are up-to-date events created for each of them.
  960. * If courseid = 0, then every chat event in the site is checked, else
  961. * only chat events belonging to the course specified are checked.
  962. * This function is used, in its new format, by restore_refresh_events()
  963. *
  964. * @param int $courseid
  965. * @return bool
  966. */
  967. function choice_refresh_events($courseid = 0) {
  968. global $DB, $CFG;
  969. require_once($CFG->dirroot.'/mod/choice/locallib.php');
  970. if ($courseid) {
  971. if (! $choices = $DB->get_records("choice", array("course" => $courseid))) {
  972. return true;
  973. }
  974. } else {
  975. if (! $choices = $DB->get_records("choice")) {
  976. return true;
  977. }
  978. }
  979. foreach ($choices as $choice) {
  980. choice_set_events($choice);
  981. }
  982. return true;
  983. }