PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/locallib.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 2023 lines | 1133 code | 255 blank | 635 comment | 231 complexity | 314aabfc644e9b57b97bf0274a86a537 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  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. * Library of internal classes and functions for module hotpot
  18. *
  19. * All the hotpot specific functions, needed to implement the module
  20. * logic, should go to here. Instead of having bunch of function named
  21. * hotpot_something() taking the hotpot instance as the first
  22. * parameter, we use a class hotpot that provides all methods.
  23. *
  24. * @package mod-hotpot
  25. * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  26. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27. */
  28. defined('MOODLE_INTERNAL') || die();
  29. require_once(dirname(__FILE__).'/lib.php'); // we extend this library here
  30. require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here
  31. /**
  32. * Full-featured hotpot API
  33. *
  34. * This wraps the hotpot database record with a set of methods that are called
  35. * from the module itself. The class should be initialized right after you get
  36. * $hotpot, $cm and $course records at the begining of the script.
  37. */
  38. class hotpot {
  39. /**
  40. * internal codes to indicate what text is to be used
  41. * for the name and introduction of a HotPot instance
  42. */
  43. const TEXTSOURCE_FILE = 0; // was TEXTSOURCE_QUIZ
  44. const TEXTSOURCE_FILENAME = 1;
  45. const TEXTSOURCE_FILEPATH = 2;
  46. const TEXTSOURCE_SPECIFIC = 3;
  47. /**
  48. * database codes to indicate what navigation aids are used
  49. * when the quiz apears in the browser
  50. */
  51. const NAVIGATION_NONE = 0; // was 6
  52. const NAVIGATION_MOODLE = 1; // was NAVIGATION_BAR
  53. const NAVIGATION_FRAME = 2;
  54. const NAVIGATION_EMBED = 3; // was NAVIGATION_IFRAME
  55. const NAVIGATION_ORIGINAL = 4;
  56. const NAVIGATION_TOPBAR = 5; // was NAVIGATION_GIVEUP but that was replaced by stopbutton
  57. /**
  58. * database codes to indicate the grading method for a HotPot instance
  59. */
  60. const GRADEMETHOD_HIGHEST = 1;
  61. const GRADEMETHOD_AVERAGE = 2;
  62. const GRADEMETHOD_FIRST = 3;
  63. const GRADEMETHOD_LAST = 4;
  64. /**
  65. * database codes to indicate the source/config location for a HotPot instance
  66. */
  67. const LOCATION_COURSEFILES = 0;
  68. const LOCATION_SITEFILES = 1;
  69. const LOCATION_WWW = 2;
  70. /**
  71. * bit-masks used to extract bits from the hotpot "title" setting
  72. */
  73. const TITLE_SOURCE = 0x03; // 1st - 2nd bits
  74. const TITLE_UNITNAME = 0x04; // 3rd bit
  75. const TITLE_SORTORDER = 0x08; // 4th bit
  76. /**
  77. * database codes for the following time fields
  78. * - timelimit : the maximum length of one attempt
  79. * - delay3 : the delay after end of quiz before control returns to Moodle
  80. */
  81. const TIME_SPECIFIC = 0;
  82. const TIME_TEMPLATE = -1;
  83. const TIME_AFTEROK = -2;
  84. const TIME_DISABLE = -3;
  85. const CONTINUE_RESUMEQUIZ = 1;
  86. const CONTINUE_RESTARTQUIZ = 2;
  87. const CONTINUE_RESTARTUNIT = 3;
  88. const CONTINUE_ABANDONUNIT = 4;
  89. const STATUS_INPROGRESS = 1;
  90. const STATUS_TIMEDOUT = 2;
  91. const STATUS_ABANDONED = 3;
  92. const STATUS_COMPLETED = 4;
  93. const STATUS_PAUSED = 5;
  94. const FEEDBACK_NONE = 0;
  95. const FEEDBACK_WEBPAGE = 1;
  96. const FEEDBACK_FORMMAIL = 2;
  97. const FEEDBACK_MOODLEFORUM = 3;
  98. const FEEDBACK_MOODLEMESSAGING = 4;
  99. const STOPBUTTON_NONE = 0;
  100. const STOPBUTTON_LANGPACK = 1;
  101. const STOPBUTTON_SPECIFIC = 2;
  102. const ACTIVITY_NONE = 0;
  103. const ACTIVITY_COURSE_ANY = -1;
  104. const ACTIVITY_SECTION_ANY = -2;
  105. const ACTIVITY_COURSE_HOTPOT = -3;
  106. const ACTIVITY_SECTION_HOTPOT = -4;
  107. const ENTRYOPTIONS_TITLE = 0x01;
  108. const ENTRYOPTIONS_GRADING = 0x02;
  109. const ENTRYOPTIONS_DATES = 0x04;
  110. const ENTRYOPTIONS_ATTEMPTS = 0x08;
  111. const EXITOPTIONS_TITLE = 0x01;
  112. const EXITOPTIONS_ENCOURAGEMENT = 0x02;
  113. const EXITOPTIONS_ATTEMPTSCORE = 0x04;
  114. const EXITOPTIONS_HOTPOTGRADE = 0x08;
  115. const EXITOPTIONS_RETRY = 0x10;
  116. const EXITOPTIONS_INDEX = 0x20;
  117. const EXITOPTIONS_COURSE = 0x40;
  118. const EXITOPTIONS_GRADES = 0x80;
  119. const BODYSTYLES_BACKGROUND = 0x01;
  120. const BODYSTYLES_COLOR = 0x02;
  121. const BODYSTYLES_FONT = 0x04;
  122. const BODYSTYLES_MARGIN = 0x08;
  123. /**
  124. * three sets of 6 bits define the times at which a quiz may be reviewed
  125. * e.g. 0x3f = 0011 1111 (i.e. right most 6 bits)
  126. */
  127. const REVIEW_DURINGATTEMPT = 0x0003f; // 1st set of 6 bits : during attempt
  128. const REVIEW_AFTERATTEMPT = 0x00fc0; // 2nd set of 6 bits : after attempt (but before quiz closes)
  129. const REVIEW_AFTERCLOSE = 0x3f000; // 3rd set of 6 bits : after the quiz closes
  130. /**
  131. * within each group of 6 bits we determine what should be shown
  132. * e.g. 0x1041 = 00-0001 0000-01 00-0001 (i.e. 3 sets of 6 bits)
  133. */
  134. const REVIEW_RESPONSES = 0x1041; // 1*0x1041 : 1st bit of each 6-bit set : Show student responses
  135. const REVIEW_ANSWERS = 0x2082; // 2*0x1041 : 2nd bit of each 6-bit set : Show correct answers
  136. const REVIEW_SCORES = 0x4104; // 3*0x1041 : 3rd bit of each 6-bit set : Show scores
  137. const REVIEW_FEEDBACK = 0x8208; // 4*0x1041 : 4th bit of each 6-bit set : Show feedback
  138. /** @var stdclass course module record */
  139. public $cm;
  140. /** @var stdclass course record */
  141. public $course;
  142. /** @var stdclass context object */
  143. public $context;
  144. /** @var int hotpot instance identifier */
  145. public $id;
  146. /** @var string hotpot activity name */
  147. public $name;
  148. /** @var string url or path of the source file for this HotPot instance */
  149. public $sourcefile;
  150. /** @var string the type of the source file for this HotPot instance */
  151. public $sourcetype;
  152. /** @var int the file itemid of the sourcefile for this HotPot instance */
  153. public $sourcelocation;
  154. /** @var string url or path of the config file for this HotPot instance */
  155. public $configfile;
  156. /** @var int the location of the configfile for this HotPot instance */
  157. public $configlocation;
  158. /** @var xxx */
  159. public $entrycm;
  160. /** @var xxx */
  161. public $entrygrade;
  162. /** @var xxx */
  163. public $entrypage;
  164. /** @var xxx */
  165. public $entrytext;
  166. /** @var xxx */
  167. public $entryformat;
  168. /** @var xxx */
  169. public $entryoptions;
  170. /** @var xxx */
  171. public $exitpage;
  172. /** @var xxx */
  173. public $exittext;
  174. /** @var xxx */
  175. public $exitformat;
  176. /** @var xxx */
  177. public $exitoptions;
  178. /** @var xxx */
  179. public $exitcm;
  180. /** @var xxx */
  181. public $exitgrade;
  182. /** @var string the output format to be used when generating browser content */
  183. public $outputformat;
  184. /** @var int navigation aids to be used when this HotPot instance appears in the browser */
  185. public $navigation;
  186. /** @var int defines what will be displayed as the title in the browser */
  187. public $title;
  188. /** @var int indicates what kind of of stop button, if any, will be displayed */
  189. public $stopbutton;
  190. /** @var string the string to be displayed on the stop button */
  191. public $stoptext;
  192. /** @var boolean flag to indicate quiz content should be run processed by Moodle filters */
  193. public $usefilters;
  194. /** @var boolean flag to indicate quiz content should be linked to Moodle glossaries */
  195. public $useglossary;
  196. /** @var string name, if any, of mediaplayer filter to be used to replace media players */
  197. public $usemediafilter;
  198. /** @var int what kind of popup, if any, should be shown for student feedback */
  199. public $studentfeedback;
  200. /** @var string url, if any, to which student feedback will be sent */
  201. public $studentfeedbackurl;
  202. /** @var int time after which this HotPot becomes available */
  203. public $timeopen;
  204. /** @var int time after which this HotPot is no longer available */
  205. public $timeclose;
  206. /** @var int the time limit for a single attempt at this HotPot */
  207. public $timelimit;
  208. /** @var int minimum time delay, in seconds, between first and second attempt */
  209. public $delay1;
  210. /** @var int minimum time delay, in seconds, between attempts after the second attempt */
  211. public $delay2;
  212. /** @var int delay, in seconds, between finishing a quiz and returning control to Moodle */
  213. public $delay3;
  214. /** @var string optional password required to access this HotPot instance */
  215. public $password;
  216. /** @var string optional IP mask to limit access this HotPot instance */
  217. public $subnet;
  218. /** @var xxx */
  219. public $reviewoptions;
  220. /** @var int 0-100 to show maximum number of attempts allowed at this HotPot instance */
  221. public $attemptlimit;
  222. /** @var int code denoting the grading method for this HotPot instance */
  223. public $grademethod;
  224. /** @var int 0-100 to show maximum grade for this HotPot instance */
  225. public $gradeweighting;
  226. /** @var boolean if true, every click of "hint", "clue" or "check" will be stored in the Moodle database */
  227. public $clickreporting;
  228. /** @var boolean if true, the raw xml returned form the attempt will be stored in the Moodle database */
  229. public $discarddetails;
  230. /** @var int timestamp of when the module was modified */
  231. public $timemodified;
  232. /** @var int timestamp of when the module was created */
  233. public $timecreated;
  234. /** @var int timestamp of when this object was created */
  235. public $time;
  236. /** @var object representing the source file */
  237. public $source;
  238. /** @var object representing the config file */
  239. public $config;
  240. /** @var object representing a record from the hotpot_attempt table */
  241. public $attempt;
  242. /** @var array cache of all attempts by current user at this HotPot activity */
  243. public $attempts;
  244. /** @var object representing the grade_grade this HotPot activity */
  245. public $gradeitem;
  246. /** @var boolean cache for hotpot:attempt capability */
  247. public $canattempt;
  248. /** @var boolean cache for hotpot:deleteallattempts capability */
  249. public $candeleteallattempts;
  250. /** @var boolean cache for hotpot:deletemyattempts capability */
  251. public $candeletemyattempts;
  252. /** @var boolean cache for hotpot:manage capability */
  253. public $canmanage;
  254. /** @var boolean cache for hotpot:preview capability */
  255. public $canpreview;
  256. /** @var boolean cache for hotpot:reviewallattempts capability */
  257. public $canreviewallattempts;
  258. /** @var boolean cache for hotpot:reviewmyattempts capability */
  259. public $canreviewmyattempts;
  260. /** @var boolean cache for hotpot:view capability */
  261. public $canview;
  262. /** @var boolean cache of swithc to show if user can start this hotpot */
  263. public $canstart;
  264. /**
  265. * Initializes the hotpot API instance using the data from DB
  266. *
  267. * Makes deep copy of all passed records properties. Replaces integer $course attribute
  268. * with a full database record (course should not be stored in instances table anyway).
  269. *
  270. * The method is "protected" to prevent it being called directly. To create a new
  271. * instance of this class please use the self::create() method (see below).
  272. *
  273. * @param stdclass $dbrecord HotPot instance data from the {hotpot} table
  274. * @param stdclass $cm Course module record as returned by {@link get_coursemodule_from_id()}
  275. * @param stdclass $course Course record from {course} table
  276. * @param stdclass $context The context of the hotpot instance
  277. * @param stdclass $attempt attempt data from the {hotpot_attempts} table
  278. */
  279. private function __construct(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null, stdclass $attempt=null) {
  280. foreach ($dbrecord as $field => $value) {
  281. if (property_exists('hotpot', $field)) {
  282. $this->$field = $value;
  283. }
  284. }
  285. $this->cm = $cm;
  286. $this->course = $course;
  287. if (is_null($context)) {
  288. $this->context = hotpot_get_context(CONTEXT_MODULE, $this->cm->id);
  289. } else {
  290. $this->context = $context;
  291. }
  292. if (is_null($attempt)) {
  293. // do nothing
  294. } else {
  295. $this->attempt = $attempt;
  296. }
  297. $this->time = time();
  298. }
  299. ////////////////////////////////////////////////////////////////////////////////
  300. // Static methods //
  301. ////////////////////////////////////////////////////////////////////////////////
  302. /**
  303. * Creates a new HotPot object
  304. *
  305. * @param stdclass $dbrecord a row from the hotpot table
  306. * @param stdclass $cm a row from the course_modules table
  307. * @param stdclass $course a row from the course table
  308. * @return hotpot the new hotpot object
  309. */
  310. static public function create(stdclass $dbrecord, stdclass $cm, stdclass $course, stdclass $context=null, stdclass $attempt=null) {
  311. return new hotpot($dbrecord, $cm, $course, $context, $attempt);
  312. }
  313. /**
  314. * set_user_editing
  315. */
  316. static public function set_user_editing() {
  317. global $USER;
  318. $editmode = optional_param('editmode', null, PARAM_BOOL);
  319. if (! is_null($editmode)) {
  320. $USER->editing = $editmode;
  321. }
  322. }
  323. /**
  324. * Returns the localized list of navigation settings for a HotPot instance
  325. *
  326. * @return array
  327. */
  328. public static function available_navigations_list() {
  329. return array (
  330. self::NAVIGATION_MOODLE => get_string('navigation_moodle', 'hotpot'),
  331. self::NAVIGATION_TOPBAR => get_string('navigation_topbar', 'hotpot'),
  332. self::NAVIGATION_FRAME => get_string('navigation_frame', 'hotpot'),
  333. self::NAVIGATION_EMBED => get_string('navigation_embed', 'hotpot'),
  334. self::NAVIGATION_ORIGINAL => get_string('navigation_original', 'hotpot'),
  335. self::NAVIGATION_NONE => get_string('navigation_none', 'hotpot')
  336. );
  337. }
  338. /**
  339. * Returns the localized list of feedback settings for a HotPot instance
  340. *
  341. * @return array
  342. */
  343. public static function available_feedbacks_list() {
  344. global $CFG;
  345. $list = array (
  346. self::FEEDBACK_NONE => get_string('none'),
  347. self::FEEDBACK_WEBPAGE => get_string('feedbackwebpage', 'hotpot'),
  348. self::FEEDBACK_FORMMAIL => get_string('feedbackformmail', 'hotpot'),
  349. self::FEEDBACK_MOODLEFORUM => get_string('feedbackmoodleforum', 'hotpot')
  350. );
  351. if ($CFG->messaging) {
  352. $list[self::FEEDBACK_MOODLEMESSAGING] = get_string('feedbackmoodlemessaging', 'hotpot');
  353. }
  354. return $list;
  355. }
  356. /**
  357. * Returns the list of media players for the HotPot module
  358. *
  359. * @return array
  360. */
  361. public static function available_mediafilters_list() {
  362. $plugins = get_list_of_plugins('mod/hotpot/mediafilter'); // sorted
  363. if (in_array('moodle', $plugins)) {
  364. // make 'moodle' the first element in the plugins array
  365. unset($plugins[array_search('moodle', $plugins)]);
  366. array_unshift($plugins, 'moodle');
  367. }
  368. // define element type for list of mediafilters (select, radio, checkbox)
  369. $options = array('' => get_string('none'));
  370. foreach ($plugins as $plugin) {
  371. $options[$plugin] = get_string('mediafilter_'.$plugin, 'hotpot');
  372. }
  373. return $options;
  374. }
  375. /**
  376. * Returns the localized list of output format setings for a given HotPot sourcetype
  377. *
  378. * @return array
  379. */
  380. public static function available_outputformats_list($sourcetype) {
  381. $outputformats = array(
  382. '0' => get_string('outputformat_best', 'hotpot')
  383. );
  384. if ($sourcetype) {
  385. $classes = self::get_classes('hotpotattempt', 'renderer.php', 'mod_', '_renderer');
  386. foreach ($classes as $class) {
  387. // use call_user_func() to prevent syntax error in PHP 5.2.x
  388. $sourcetypes = call_user_func(array($class, 'sourcetypes'));
  389. if (in_array($sourcetype, $sourcetypes)) {
  390. // strip prefix, "mod_hotpot_attempt_", and suffix, "_renderer"
  391. $outputformat = substr($class, 19, -9);
  392. $outputformats[$outputformat] = get_string('outputformat_'.$outputformat, 'hotpot');
  393. }
  394. }
  395. // remove "best" if there is only one compatible output format
  396. // if (count($outputformats)==2) {
  397. // unset($outputformats[0]);
  398. // }
  399. }
  400. return $outputformats;
  401. }
  402. /**
  403. * Returns the localized list of attempt limit settings for a HotPot instance
  404. *
  405. * @return array
  406. */
  407. public static function available_attemptlimits_list() {
  408. $options = array(
  409. 0 => get_string('attemptsunlimited', 'hotpot'),
  410. );
  411. for ($i=1; $i<=10; $i++) {
  412. $options[$i] = "$i";
  413. }
  414. return $options;
  415. }
  416. /**
  417. * Returns the localized list of grade method settings for a HotPot instance
  418. *
  419. * @return array
  420. */
  421. public static function available_grademethods_list() {
  422. return array (
  423. self::GRADEMETHOD_HIGHEST => get_string('highestscore', 'hotpot'),
  424. self::GRADEMETHOD_AVERAGE => get_string('averagescore', 'hotpot'),
  425. self::GRADEMETHOD_FIRST => get_string('firstattempt', 'hotpot'),
  426. self::GRADEMETHOD_LAST => get_string('lastattempt', 'hotpot'),
  427. );
  428. }
  429. /**
  430. * Returns the localized list of status settings for a HotPot attempt
  431. *
  432. * @return array
  433. */
  434. public static function available_statuses_list() {
  435. return array (
  436. self::STATUS_INPROGRESS => get_string('inprogress', 'hotpot'),
  437. self::STATUS_TIMEDOUT => get_string('timedout', 'hotpot'),
  438. self::STATUS_ABANDONED => get_string('abandoned', 'hotpot'),
  439. self::STATUS_COMPLETED => get_string('completed', 'hotpot')
  440. );
  441. }
  442. /**
  443. * Returns the localized list of grade method settings for a HotPot instance
  444. *
  445. * @return array
  446. */
  447. public static function available_namesources_list() {
  448. return array (
  449. self::TEXTSOURCE_FILE => get_string('textsourcefile', 'hotpot'),
  450. self::TEXTSOURCE_FILENAME => get_string('textsourcefilename', 'hotpot'),
  451. self::TEXTSOURCE_FILEPATH => get_string('textsourcefilepath', 'hotpot'),
  452. self::TEXTSOURCE_SPECIFIC => get_string('textsourcespecific', 'hotpot')
  453. );
  454. }
  455. /**
  456. * Returns the localized list of grade method settings for a HotPot instance
  457. *
  458. * @return array
  459. */
  460. public static function available_titles_list() {
  461. return array (
  462. self::TEXTSOURCE_SPECIFIC => get_string('hotpotname', 'hotpot'),
  463. self::TEXTSOURCE_FILE => get_string('textsourcefile', 'hotpot'),
  464. self::TEXTSOURCE_FILENAME => get_string('textsourcefilename', 'hotpot'),
  465. self::TEXTSOURCE_FILEPATH => get_string('textsourcefilepath', 'hotpot')
  466. );
  467. }
  468. /**
  469. * Returns the localized list of maximum grade settings for a HotPot instance
  470. *
  471. * @return array
  472. */
  473. public static function available_gradeweightings_list() {
  474. $options = array();
  475. for ($i=100; $i>=1; $i--) {
  476. $options[$i] = $i;
  477. }
  478. $options[0] = get_string('nograde');
  479. return $options;
  480. }
  481. /**
  482. * Detects the type of the source file
  483. *
  484. * @param stored_file $sourcefile the file that has just been uploaded and stored
  485. * @return string the type of the source file (e.g. hp_6_jcloze_xml)
  486. */
  487. public static function get_sourcetype($sourcefile) {
  488. // include all the hotpot_source classes
  489. $classes = self::get_classes('hotpotsource');
  490. // loop through the classes checking to see if this file is recognized
  491. // use call_user_func() to prevent syntax error in PHP 5.2.x
  492. foreach ($classes as $class) {
  493. if (call_user_func(array($class, 'is_quizfile'), $sourcefile)) {
  494. return call_user_func(array($class, 'get_type'), $class);
  495. }
  496. }
  497. // file is not a recognized quiz type :-(
  498. return '';
  499. }
  500. /**
  501. * Returns a js module object for the HotPot module
  502. *
  503. * @param array $requires
  504. * e.g. array('base', 'dom', 'event-delegate', 'event-key')
  505. * @return array $strings
  506. * e.g. array(
  507. * array('timesup', 'quiz'),
  508. * array('functiondisabledbysecuremode', 'quiz'),
  509. * array('flagged', 'question')
  510. * )
  511. */
  512. public static function get_js_module(array $requires = null, array $strings = null) {
  513. return array(
  514. 'name' => 'mod_hotpot',
  515. 'fullpath' => '/mod/hotpot/module.js',
  516. 'requires' => $requires,
  517. 'strings' => $strings,
  518. );
  519. }
  520. /**
  521. * get_version_info
  522. *
  523. * @param xxx $info
  524. * @return xxx
  525. */
  526. public static function get_version_info($info) {
  527. global $CFG;
  528. static $module = null;
  529. if (is_null($module)) {
  530. $module = new stdClass();
  531. require($CFG->dirroot.'/mod/hotpot/version.php');
  532. }
  533. if (isset($module->$info)) {
  534. return $module->$info;
  535. } else {
  536. return "no $info found";
  537. }
  538. }
  539. /**
  540. * load_mediafilter_filter
  541. *
  542. * @param xxx $classname
  543. */
  544. public static function load_mediafilter_filter($classname) {
  545. global $CFG;
  546. $path = $CFG->dirroot.'/mod/hotpot/mediafilter/'.$classname.'/class.php';
  547. // check the filter exists
  548. if (! file_exists($path)) {
  549. debugging('hotpot mediafilter class is not accessible: '.$classname, DEBUG_DEVELOPER);
  550. return false;
  551. }
  552. return require_once($path);
  553. }
  554. /**
  555. * sourcefile_options
  556. *
  557. * @param xxx $context
  558. * @return xxx
  559. */
  560. public static function sourcefile_options() {
  561. return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => -1);
  562. }
  563. /**
  564. * text_editors_options
  565. *
  566. * @param xxx $context
  567. * @return xxx
  568. */
  569. public static function text_editors_options($context) {
  570. return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => EDITOR_UNLIMITED_FILES,
  571. 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0);
  572. }
  573. /**
  574. * text_page_types
  575. *
  576. * @return xxx
  577. */
  578. public static function text_page_types() {
  579. return array('entry', 'exit');
  580. }
  581. /**
  582. * text_page_options
  583. *
  584. * @param xxx $type
  585. * @return xxx
  586. */
  587. public static function text_page_options($type) {
  588. if ($type=='entry') {
  589. return array(
  590. 'title' => self::ENTRYOPTIONS_TITLE,
  591. 'grading' => self::ENTRYOPTIONS_GRADING,
  592. 'dates' => self::ENTRYOPTIONS_DATES,
  593. 'attempts' => self::ENTRYOPTIONS_ATTEMPTS
  594. );
  595. }
  596. if ($type=='exit') {
  597. return array(
  598. 'title' => self::ENTRYOPTIONS_TITLE,
  599. 'encouragement' => self::EXITOPTIONS_ENCOURAGEMENT,
  600. 'attemptscore' => self::EXITOPTIONS_ATTEMPTSCORE,
  601. 'hotpotgrade' => self::EXITOPTIONS_HOTPOTGRADE,
  602. 'retry' => self::EXITOPTIONS_RETRY,
  603. 'index' => self::EXITOPTIONS_INDEX,
  604. 'course' => self::EXITOPTIONS_COURSE,
  605. 'grades' => self::EXITOPTIONS_GRADES
  606. );
  607. }
  608. return array();
  609. }
  610. /**
  611. * user_preferences_fields
  612. *
  613. * @return array of user_preferences used by the HotPot module
  614. */
  615. public static function user_preferences_fieldnames() {
  616. return array(
  617. // fields used only when adding a new HotPot
  618. 'namesource','entrytextsource','exittextsource','quizchain',
  619. // source/config files
  620. 'sourcefile','sourcelocation','configfile','configlocation',
  621. // entry/exit pages
  622. 'entrypage','entryformat','entryoptions',
  623. 'exitpage','exitformat','exitoptions',
  624. 'entrycm','entrygrade','exitcm','exitgrade',
  625. // display
  626. 'outputformat','navigation','title','stopbutton','stoptext',
  627. 'usefilters','useglossary','usemediafilter','studentfeedback','studentfeedbackurl',
  628. // access restrictions
  629. 'timeopen','timeclose','timelimit','delay1','delay2','delay3',
  630. 'password','subnet','reviewoptions','attemptlimit',
  631. // grading and reporting
  632. 'grademethod','gradeweighting','clickreporting','discarddetails'
  633. );
  634. }
  635. /**
  636. * string_ids
  637. *
  638. * @param xxx $field_value
  639. * @return xxx
  640. */
  641. public static function string_ids($field_value, $max_field_length=255) {
  642. $ids = array();
  643. $strings = explode(',', $field_value);
  644. foreach($strings as $str) {
  645. if ($id = self::string_id($str)) {
  646. $ids[] = $id;
  647. }
  648. }
  649. $ids = implode(',', $ids);
  650. // we have to make sure that the list of $ids is no longer
  651. // than the maximum allowable length for this field
  652. if (strlen($ids) > $max_field_length) {
  653. // truncate $ids just before last comma in allowable field length
  654. // Note: largest possible id is something like 9223372036854775808
  655. // so we must leave space for that in the $ids string
  656. $ids = substr($ids, 0, $max_field_length - 20);
  657. $ids = substr($ids, 0, strrpos($ids, ','));
  658. // create single $str(ing) containing all $strings not included in $ids
  659. $str = implode(',', array_slice($strings, substr_count($ids, ',') + 1));
  660. // append the id of the string containing all the strings not yet in $ids
  661. if ($id = self::string_id($str)) {
  662. $ids .= ','.$id;
  663. }
  664. }
  665. // return comma separated list of string $ids
  666. return $ids;
  667. }
  668. /**
  669. * string_id
  670. *
  671. * @param xxx $str
  672. * @return xxx
  673. */
  674. public static function string_id($str) {
  675. global $DB;
  676. if (! isset($str) || ! is_string($str) || trim($str)=='') {
  677. // invalid input string
  678. return false;
  679. }
  680. // create md5 key
  681. $md5key = md5($str);
  682. if ($id = $DB->get_field('hotpot_strings', 'id', array('md5key'=>$md5key))) {
  683. // string already exists
  684. return $id;
  685. }
  686. // create a new string record
  687. $record = (object)array('string'=>$str, 'md5key'=>$md5key);
  688. if (! $id = $DB->insert_record('hotpot_strings', $record)) {
  689. print_error('error_insertrecord', 'hotpot', '', 'hotpot_strings');
  690. }
  691. // new string was successfully added
  692. return $id;
  693. }
  694. /**
  695. * get_strings
  696. *
  697. * @param xxx $ids
  698. * @return xxx
  699. */
  700. public static function get_strings($ids) {
  701. global $DB;
  702. // convert $ids to an array, if necessary
  703. if (is_string($ids)) {
  704. $ids = explode(',', $ids);
  705. $ids = array_filter($ids);
  706. }
  707. // return strings, if any
  708. if (empty($ids)) {
  709. return array();
  710. } else {
  711. list($filter, $params) = $DB->get_in_or_equal($ids);
  712. return $DB->get_records_select('hotpot_strings', "id $filter", $params, '', 'id,string');
  713. }
  714. }
  715. /**
  716. * get_question_text
  717. *
  718. * @param xxx $question
  719. * @return xxx
  720. */
  721. static public function get_question_text($question) {
  722. global $DB;
  723. if (empty($question->text)) {
  724. // JMatch, JMix and JQuiz
  725. return $question->name;
  726. } else {
  727. // JCloze and JCross
  728. return $DB->get_field('hotpot_strings', 'string', array('id' => $question->text));
  729. }
  730. }
  731. ////////////////////////////////////////////////////////////////////////////////
  732. // Hotpot API //
  733. ////////////////////////////////////////////////////////////////////////////////
  734. /**
  735. * @return moodle_url of this hotpot's view page
  736. */
  737. public function view_url($cm=null) {
  738. if (is_null($cm)) {
  739. $cm = $this->cm;
  740. }
  741. return new moodle_url('/mod/'.$cm->modname.'/view.php', array('id' => $cm->id));
  742. }
  743. /**
  744. * @return moodle_url of this hotpot's view page
  745. */
  746. public function report_url($mode='', $cm=null) {
  747. if (is_null($cm)) {
  748. $cm = $this->cm;
  749. }
  750. $params = array('id' => $cm->id);
  751. if ($mode) {
  752. $params['mode'] = $mode;
  753. }
  754. return new moodle_url('/mod/hotpot/report.php', $params);
  755. }
  756. /**
  757. * @return moodle_url of this hotpot's attempt page
  758. */
  759. public function attempt_url($framename='', $cm=null) {
  760. if (is_null($cm)) {
  761. $cm = $this->cm;
  762. }
  763. $params = array('id' => $cm->id);
  764. if ($framename) {
  765. $params['framename'] = $framename;
  766. }
  767. return new moodle_url('/mod/hotpot/attempt.php', $params);
  768. }
  769. /**
  770. * @return moodle_url of this hotpot's attempt page
  771. */
  772. public function submit_url($attempt=null) {
  773. if (is_null($attempt)) {
  774. $attempt = $this->attempt;
  775. }
  776. return new moodle_url('/mod/hotpot/submit.php', array('id' => $attempt->id));
  777. }
  778. /**
  779. * @return moodle_url of the review page for an attempt at this hotpot
  780. */
  781. public function review_url($attempt=null) {
  782. if (is_null($attempt)) {
  783. $attempt = $this->attempt;
  784. }
  785. return new moodle_url('/mod/hotpot/review.php', array('id' => $attempt->id));
  786. }
  787. /**
  788. * @return moodle_url of this course's hotpot index page
  789. */
  790. public function index_url($course=null) {
  791. if (is_null($course)) {
  792. $course = $this->course;
  793. }
  794. return new moodle_url('/mod/hotpot/index.php', array('id' => $course->id));
  795. }
  796. /**
  797. * @return moodle_url of this hotpot's course page
  798. */
  799. public function course_url($course=null) {
  800. if (is_null($course)) {
  801. $course = $this->course;
  802. }
  803. $params = array('id' => $course->id);
  804. $sectionnum = 0;
  805. if (isset($course->coursedisplay) && defined('COURSE_DISPLAY_MULTIPAGE')) {
  806. // Moodle >= 2.3
  807. if ($course->coursedisplay==COURSE_DISPLAY_MULTIPAGE) {
  808. $courseid = $course->id;
  809. $sectionid = $this->cm->section;
  810. if ($modinfo = get_fast_modinfo($this->course)) {
  811. $sections = $modinfo->get_section_info_all();
  812. foreach ($sections as $section) {
  813. if ($section->id==$sectionid) {
  814. $sectionnum = $section->section;
  815. break;
  816. }
  817. }
  818. }
  819. unset($modinfo, $sections, $section);
  820. }
  821. }
  822. if ($sectionnum) {
  823. $params['section'] = $sectionnum;
  824. }
  825. return new moodle_url('/course/view.php', $params);
  826. }
  827. /**
  828. * @return moodle_url of this hotpot's course grade page
  829. */
  830. public function grades_url($course=null) {
  831. if (is_null($course)) {
  832. $course = $this->course;
  833. }
  834. return new moodle_url('/grade/index.php', array('id' => $course->id));
  835. }
  836. /**
  837. * @return source object representing the source file for this HotPot
  838. */
  839. public function get_source() {
  840. global $CFG, $DB;
  841. if (empty($this->source)) {
  842. // get sourcetype e.g. hp_6_jcloze_xml
  843. $sourcefile = $this->get_sourcefile();
  844. if (! $sourcetype = clean_param($this->sourcetype, PARAM_SAFEDIR)) {
  845. if ($sourcetype = hotpot::get_sourcetype($sourcefile)) {
  846. $DB->set_field('hotpot', 'sourcetype', $sourcetype, array('id' => $this->id));
  847. $this->sourcetype = $sourcetype;
  848. } else {
  849. throw new moodle_exception('missingsourcetype', 'hotpot');
  850. }
  851. }
  852. $dir = str_replace('_', '/', $sourcetype);
  853. require_once($CFG->dirroot.'/mod/hotpot/source/'.$dir.'/class.php');
  854. $classname = 'hotpot_source_'.$sourcetype;
  855. $this->source = new $classname($sourcefile, $this);
  856. }
  857. return $this->source;
  858. }
  859. /**
  860. * Returns the localized description of the grade method
  861. *
  862. * @return string
  863. */
  864. public function format_grademethod() {
  865. $options = $this->available_grademethods_list();
  866. if (array_key_exists($this->grademethod, $options)) {
  867. return $options[$this->grademethod];
  868. } else {
  869. return $this->grademethod; // shouldn't happen
  870. }
  871. }
  872. /**
  873. * Returns the localized description of the attempt status
  874. *
  875. * @return string
  876. */
  877. public static function format_status($status) {
  878. $options = self::available_statuses_list();
  879. if (array_key_exists($status, $options)) {
  880. return $options[$status];
  881. } else {
  882. return $status; // shouldn't happen
  883. }
  884. }
  885. /**
  886. * Returns a formatted version of the $time
  887. *
  888. * @param in $time the time to format
  889. * @param string $format time format string
  890. * @param string $notime return value if $time==0
  891. * @return string
  892. */
  893. public static function format_time($time, $format=null, $notime='&nbsp;') {
  894. if ($time>0) {
  895. return format_time($time, $format);
  896. } else {
  897. return $notime;
  898. }
  899. }
  900. /**
  901. * Returns a formatted version of an (attempt) $record's score
  902. *
  903. * @param object $record from the Moodle database
  904. * @param string $noscore return value if $record->score is not set
  905. * @return string
  906. */
  907. function format_score($record, $default='&nbsp;') {
  908. if (isset($record->score)) {
  909. return $record->score;
  910. } else {
  911. return $default;
  912. }
  913. }
  914. /**
  915. * Returns the stored_file object for this HotPot's source file
  916. *
  917. * @return stored_file
  918. */
  919. public function get_sourcefile() {
  920. global $CFG, $DB;
  921. $fs = get_file_storage();
  922. $filename = basename($this->sourcefile);
  923. $filepath = dirname($this->sourcefile);
  924. // require leading and trailing slash on $filepath
  925. if (substr($filepath, 0, 1)=='/' && substr($filepath, -1)=='/') {
  926. // do nothing - $filepath is valid
  927. } else {
  928. // fix filepath - shouldn't happen !!
  929. // maybe leftover from a messy upgrade
  930. if ($filepath=='.' || $filepath=='') {
  931. $filepath = '/';
  932. } else {
  933. $filepath = '/'.ltrim($filepath, '/');
  934. $filepath = rtrim($filepath, '/').'/';
  935. }
  936. $this->sourcefile = $filepath.$filename;
  937. $DB->set_field('hotpot', 'sourcefile', $this->sourcefile, array('id' => $this->id));
  938. }
  939. if ($file = $fs->get_file($this->context->id, 'mod_hotpot', 'sourcefile', 0, $filepath, $filename)) {
  940. return $file;
  941. }
  942. // the source file is missing, probably this HotPot
  943. // has recently been upgraded/imported from Moodle 1.9
  944. // so we are going to try to create the missing stored file
  945. $file_record = array(
  946. 'contextid'=>$this->context->id, 'component'=>'mod_hotpot', 'filearea'=>'sourcefile',
  947. 'sortorder'=>1, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename
  948. );
  949. $coursecontext = hotpot_get_context(CONTEXT_COURSE, $this->course->id);
  950. $filehash = sha1('/'.$coursecontext->id.'/course/legacy/0'.$filepath.$filename);
  951. if ($file = $fs->get_file_by_hash($filehash)) {
  952. // file exists in legacy course files
  953. if ($file = $fs->create_file_from_storedfile($file_record, $file)) {
  954. return $file;
  955. }
  956. }
  957. $oldfilepath = $CFG->dataroot.'/'.$this->course->id.$filepath.$filename;
  958. if (file_exists($oldfilepath)) {
  959. // file exists on server's filesystem
  960. if ($file = $fs->create_file_from_pathname($file_record, $oldfilepath)) {
  961. return $file;
  962. }
  963. }
  964. // source file not found - shouldn't happen !!
  965. throw new moodle_exception('sourcefilenotfound', 'hotpot', '', $this->sourcefile);
  966. }
  967. /**
  968. * Returns the output format to be used for an attempt at this HotPot
  969. * If the outputformat is not given, the "best" outupt format is returned
  970. * which is the one with the same name as "sourcetype" for this HotPot
  971. *
  972. * @return string $subtype
  973. */
  974. public function get_outputformat() {
  975. if (empty($this->outputformat)) {
  976. return $this->get_source()->get_best_outputformat();
  977. } else {
  978. return clean_param($this->outputformat, PARAM_SAFEDIR);
  979. }
  980. }
  981. /**
  982. * can_attempt
  983. *
  984. * @return xxx
  985. */
  986. function can_attempt() {
  987. if (is_null($this->canattempt)) {
  988. $this->canattempt = has_capability('mod/hotpot:attempt', $this->context);
  989. }
  990. return $this->canattempt;
  991. }
  992. /**
  993. * can_deleteattempts
  994. *
  995. * @return xxx
  996. */
  997. function can_deleteattempts() {
  998. return $this->can_deletemyattempts() || $this->can_deleteallattempts();
  999. }
  1000. /**
  1001. * can_deleteallattempts
  1002. *
  1003. * @return xxx
  1004. */
  1005. function can_deleteallattempts() {
  1006. if (is_null($this->candeleteallattempts)) {
  1007. $this->candeleteallattempts = has_capability('mod/hotpot:deleteallattempts', $this->context);
  1008. }
  1009. return $this->candeleteallattempts;
  1010. }
  1011. /**
  1012. * can_deletemyattempts
  1013. *
  1014. * @return xxx
  1015. */
  1016. function can_deletemyattempts() {
  1017. if (is_null($this->candeletemyattempts)) {
  1018. $this->candeletemyattempts = has_capability('mod/hotpot:deletemyattempts', $this->context);
  1019. }
  1020. return $this->candeletemyattempts;
  1021. }
  1022. /**
  1023. * can_manage
  1024. *
  1025. * @return xxx
  1026. */
  1027. function can_manage() {
  1028. if (is_null($this->canmanage)) {
  1029. $this->canmanage = has_capability('mod/hotpot:manage', $this->context);
  1030. }
  1031. return $this->canmanage;
  1032. }
  1033. /**
  1034. * can_preview
  1035. *
  1036. * @return xxx
  1037. */
  1038. function can_preview() {
  1039. if (is_null($this->canpreview)) {
  1040. $this->canpreview = has_capability('mod/hotpot:preview', $this->context);
  1041. }
  1042. return $this->canpreview;
  1043. }
  1044. /**
  1045. * can_reviewallattempts
  1046. *
  1047. * @return xxx
  1048. */
  1049. function can_reviewattempts() {
  1050. return $this->can_reviewmyattempts() || $this->can_reviewallattempts();
  1051. }
  1052. /**
  1053. * can_reviewallattempts
  1054. *
  1055. * @return xxx
  1056. */
  1057. function can_reviewallattempts() {
  1058. if (is_null($this->canreviewallattempts)) {
  1059. $this->canreviewallattempts = has_capability('mod/hotpot:reviewallattempts', $this->context);
  1060. }
  1061. return $this->canreviewallattempts;
  1062. }
  1063. /**
  1064. * can_reviewmyattempts
  1065. *
  1066. * @return xxx
  1067. */
  1068. function can_reviewmyattempts() {
  1069. if (is_null($this->canreviewmyattempts)) {
  1070. $this->canreviewmyattempts = has_capability('mod/hotpot:reviewmyattempts', $this->context);
  1071. }
  1072. return $this->canreviewmyattempts;
  1073. }
  1074. /**
  1075. * can_view
  1076. *
  1077. * @return xxx
  1078. */
  1079. function can_view() {
  1080. if (is_null($this->canview)) {
  1081. $this->canview = has_capability('mod/hotpot:view', $this->context);
  1082. }
  1083. return $this->canview;
  1084. }
  1085. /**
  1086. * can_start
  1087. *
  1088. * @param xxx $canstart (optional, default=null)
  1089. * @return xxx
  1090. */
  1091. function can_start($canstart=null) {
  1092. if (is_null($canstart)) {
  1093. if (is_null($this->canstart)) {
  1094. // set automatically
  1095. if (! $this->can_attempt()) {
  1096. $this->canstart = false;
  1097. } else if ($this->require_isopen()) {
  1098. $this->canstart = false;
  1099. } else if ($this->require_notclosed()) {
  1100. $this->canstart = false;
  1101. } else if ($this->require_entrycm()) {
  1102. $this->canstart = false;
  1103. } else if ($this->require_delay('delay1')) {
  1104. $this->canstart = false;
  1105. } else if ($this->require_delay('delay2')) {
  1106. $this->canstart = false;
  1107. } else if ($this->require_moreattempts(true)) {
  1108. $this->canstart = false;
  1109. } else { // no errors so far
  1110. $this->canstart = true;
  1111. }
  1112. }
  1113. // get
  1114. return $this->canstart;
  1115. } else {
  1116. // set manually
  1117. $this->canstart = $canstart;
  1118. }
  1119. }
  1120. /**
  1121. * Returns the subtype to be used to get a renderer for an attempt at this HotPot
  1122. *
  1123. * @return string $subtype
  1124. */
  1125. public function get_attempt_renderer_subtype() {
  1126. return 'attempt_'.$this->get_outputformat();
  1127. }
  1128. /**
  1129. * set_preferred_pagelayout
  1130. *
  1131. * @param xxx $PAGE
  1132. */
  1133. public function set_preferred_pagelayout($PAGE) {
  1134. // page layouts are defined in theme/xxx/config.php
  1135. switch ($this->navigation) {
  1136. case self::NAVIGATION_ORIGINAL:
  1137. case self::NAVIGATION_NONE:
  1138. // $PAGE->set_pagelayout('popup');
  1139. $PAGE->set_pagelayout('embedded');
  1140. break;
  1141. case self::NAVIGATION_FRAME:
  1142. case self::NAVIGATION_EMBED:
  1143. $framename = optional_param('framename', '', PARAM_ALPHA);
  1144. if ($framename=='top') {
  1145. $PAGE->set_pagelayout('frametop');
  1146. }
  1147. if ($framename=='main') {
  1148. $PAGE->set_pagelayout('embedded');
  1149. }
  1150. break;
  1151. case self::NAVIGATION_TOPBAR:
  1152. $PAGE->set_pagelayout('login'); // no nav menu
  1153. break;
  1154. }
  1155. }
  1156. /**
  1157. * to_stdclass
  1158. *
  1159. * @return xxx
  1160. */
  1161. public function to_stdclass() {
  1162. $stdclass = new stdclass();
  1163. $vars = get_object_vars($this);
  1164. foreach ($vars as $name => $value) {
  1165. if (is_object($this->$name) || is_array($this->$name)) {
  1166. continue;
  1167. }
  1168. $stdclass->$name = $value;
  1169. }
  1170. // extra fields required for grades
  1171. if (isset($this->course) && is_object($this->course)) {
  1172. $stdclass->course = $this->course->id;
  1173. }
  1174. if (isset($this->cm) && is_object($this->cm)) {
  1175. $stdclass->cmidnumber = $this->cm->id;
  1176. }
  1177. $stdclass->modname = 'hotpot';
  1178. return $stdclass;
  1179. }
  1180. /**
  1181. * Returns the subtype to be used to get a report renderer for this HotPot
  1182. *
  1183. * @return string $mode
  1184. * @return string $subtype
  1185. */
  1186. public function get_report_renderer_subtype($mode) {
  1187. if ($mode=='') {
  1188. $mode = 'overview';
  1189. }
  1190. return 'report_'.$mode;
  1191. }
  1192. ////////////////////////////////////////////////////////////////////////////////
  1193. // Internal methods (implementation details) //
  1194. ////////////////////////////////////////////////////////////////////////////////
  1195. /**
  1196. * This function will "include" all the files matching $classfilename for a given a plugin type
  1197. * (e.g. hotpotsource), and return a list of classes that were included
  1198. *
  1199. * @param string $plugintype one of the plugintypes specified in mod/hotpot/db/subplugins.php
  1200. */
  1201. static public function get_classes($plugintype, $classfilename='class.php', $prefix='', $suffix='') {
  1202. global $CFG;
  1203. // initialize array to hold class names
  1204. $classes = array();
  1205. // get list of all subplugins
  1206. $subplugins = array();
  1207. include($CFG->dirroot.'/mod/hotpot/db/subplugins.php');
  1208. // extract the $plugintype we are interested in
  1209. $types = array();
  1210. if (isset($subplugins[$plugintype])) {
  1211. $types[$plugintype] = $subplugins[$plugintype];
  1212. }
  1213. unset($subplugins);
  1214. // we are not interested in these directories
  1215. $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
  1216. // get all the subplugins for this $plugintype
  1217. while (list($type, $dir) = each($types)) {
  1218. $fulldir = $CFG->dirroot.'/'.$dir;
  1219. if (is_dir($fulldir) && file_exists($fulldir.'/'.$classfilename)) {
  1220. // include the class
  1221. require_once($fulldir.'/'.$classfilename);
  1222. // extract class name, e.g. hotpot_source_hp_6_jcloze_xml
  1223. // from $subdir, e.g. mod/hotpot/file/h6/6/jcloze/xml
  1224. // by removing leading "mod/" and converting all "/" to "_"
  1225. $classes[] = $prefix.str_replace('/', '_', substr($dir, 4)).$suffix;
  1226. // get subplugins in this $dir
  1227. $items = new DirectoryIterator($fulldir);
  1228. foreach ($items as $item) {
  1229. if (substr($item, 0, 1)=='.' || in_array($item, $ignored)) {
  1230. continue;
  1231. }
  1232. if ($item->isDir()) {
  1233. $types[$type.$item] = $dir.'/'.$item;
  1234. }
  1235. }
  1236. }
  1237. }
  1238. sort($classes);
  1239. return $classes;
  1240. }
  1241. /**
  1242. * get_report_modes
  1243. *
  1244. * @return xxx
  1245. */
  1246. function get_report_modes() {
  1247. $modes = array('overview', 'scores', 'responses', 'analysis');
  1248. if ($this->clickreporting) {
  1249. $modes[] = 'clicktrail';
  1250. }
  1251. return $modes;
  1252. }
  1253. ////////////////////////////////////////////////////////////////////////////////
  1254. // methods to access database records connected to this HotPot //
  1255. ////////////////////////////////////////////////////////////////////////////////
  1256. /*
  1257. * create an db record for an attempt at this HotPot activity
  1258. *
  1259. * @return int the id of the newly created attempt record
  1260. */
  1261. function create_attempt() {
  1262. global $DB, $USER;
  1263. if (empty($this->attempt)) {
  1264. // get max attempt number so far
  1265. $sql = "SELECT MAX(attempt) FROM {hotpot_attempts} WHERE hotpotid=? AND userid=?";
  1266. if ($max_attempt = $DB->get_field_sql($sql, array($this->id, $USER->id))) {
  1267. $max_attempt ++;
  1268. } else {
  1269. $max_attempt = 1;
  1270. }
  1271. // create attempt record
  1272. $this->attempt = new stdClass();
  1273. $this->attempt->hotpotid = $this->id;
  1274. $this->attempt->userid = $USER->id;
  1275. $this->attempt->starttime = 0;
  1276. $this->attempt->endtime = 0;
  1277. $this->attempt->score = 0;
  1278. $this->attempt->penalties = 0;
  1279. $this->attempt->attempt = $max_attempt;
  1280. $this->attempt->timestart = $this->time;
  1281. $this->attempt->timefinish = 0;
  1282. $this->attempt->status = self::STATUS_INPROGRESS;
  1283. $this->attempt->clickreportid = 0;
  1284. $this->attempt->timemodified = $this->time;
  1285. // insert attempt record into database
  1286. if (! $this->attempt->id = $DB->insert_record('hotpot_attempts', $this->attempt)) {
  1287. throw new moodle_exception('error_insertrecord', 'hotpot', '', 'hotpot_attempts');
  1288. }
  1289. // set previous "in progress" attempt(s) to adandoned
  1290. $select = 'hotpotid=? AND userid=? AND attempt<=? AND status=?';
  1291. $params = array($this->id, $USER->id, $max_attempt, self::STATUS_INPROGRESS);
  1292. if ($attempts = $DB->get_records_select('hotpot_attempts', $select, $params)) {
  1293. foreach ($attempts as $attempt) {
  1294. $attempt->timemodified = $this->time;
  1295. $attempt->status = self::STATUS_ABANDONED;

Large files files are truncated, but you can click here to view the full file