PageRenderTime 102ms CodeModel.GetById 32ms RepoModel.GetById 7ms app.codeStats 0ms

/lib.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 1833 lines | 1049 code | 221 blank | 563 comment | 195 complexity | eb6c1b91752c288ad61e4af65d9d831b 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 detail.
  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 hotpot module functions needed by Moodle core and other subsystems
  18. *
  19. * All the functions neeeded by Moodle core, gradebook, file subsystem etc
  20. * are placed here.
  21. *
  22. * @package mod
  23. * @subpackage hotpot
  24. * @copyright 2009 Gordon Bateson <gordon.bateson@gmail.com>
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26. */
  27. defined('MOODLE_INTERNAL') || die();
  28. ////////////////////////////////////////////////////////////////////////////////
  29. // Moodle core API //
  30. ////////////////////////////////////////////////////////////////////////////////
  31. /**
  32. * Returns the information if the module supports a feature
  33. *
  34. * the very latest Moodle 2.x expects "mod_hotpot_supports"
  35. * but since this module may also be run in early Moodle 2.x
  36. * we leave this function with its legacy name "hotpot_supports"
  37. *
  38. * @see plugin_supports() in lib/moodlelib.php
  39. * @see init_features() in course/moodleform_mod.php
  40. * @param string $feature FEATURE_xx constant for requested feature
  41. * @return mixed true if the feature is supported, null if unknown
  42. */
  43. function hotpot_supports($feature) {
  44. switch($feature) {
  45. // enable features whose default is "false"
  46. case FEATURE_GRADE_HAS_GRADE: return true;
  47. case FEATURE_GROUPINGS: return true;
  48. case FEATURE_GROUPMEMBERSONLY: return true;
  49. case FEATURE_BACKUP_MOODLE2: return true;
  50. // use default for these features whose default is "false"
  51. //case FEATURE_RATE: return false;
  52. //case FEATURE_GRADE_HAS_GRADE: return false;
  53. //case FEATURE_COMPLETION_TRACKS_VIEWS: return false;
  54. // disable features whose default is "true"
  55. case FEATURE_MOD_INTRO: return false;
  56. // use default for these features whose default is "true"
  57. //case FEATURE_GROUPS: return true;
  58. //case FEATURE_IDNUMBER: return true;
  59. //case FEATURE_GRADE_OUTCOMES: return true;
  60. //case FEATURE_MODEDIT_DEFAULT_COMPLETION: return true;
  61. // otherwise, this is some feature we do not know about
  62. default: return null;
  63. }
  64. }
  65. /**
  66. * Saves a new instance of the hotpot into the database
  67. *
  68. * Given an object containing all the necessary data,
  69. * (defined by the form in mod_form.php) this function
  70. * will save a new instance and return the id number
  71. * of the new instance.
  72. *
  73. * @param stdclass $data An object from the form in mod_form.php
  74. * @return int The id of the newly inserted hotpot record
  75. */
  76. function hotpot_add_instance(stdclass $data, $mform) {
  77. global $DB;
  78. hotpot_process_formdata($data, $mform);
  79. // insert the new record so we get the id
  80. $data->id = $DB->insert_record('hotpot', $data);
  81. // update calendar events
  82. hotpot_update_events_wrapper($data);
  83. // update gradebook item
  84. hotpot_grade_item_update($data);
  85. return $data->id;
  86. }
  87. /**
  88. * Given an object containing all the necessary data,
  89. * (defined by the form in mod_form.php) this function
  90. * will update an existing instance with new data.
  91. *
  92. * @param stdclass $data An object from the form in mod_form.php
  93. * @return bool success
  94. */
  95. function hotpot_update_instance(stdclass $data, $mform) {
  96. global $DB;
  97. hotpot_process_formdata($data, $mform);
  98. $data->id = $data->instance;
  99. $DB->update_record('hotpot', $data);
  100. // update calendar events
  101. hotpot_update_events_wrapper($data);
  102. // update gradebook item
  103. if ($data->grademethod==$mform->get_original_value('grademethod', 0)) {
  104. hotpot_grade_item_update($data);
  105. } else {
  106. // recalculate grades for all users
  107. hotpot_update_grades($data);
  108. }
  109. return true;
  110. }
  111. /**
  112. * Set secondary fields (i.e. fields derived from the form fields)
  113. * for this HotPot acitivity
  114. *
  115. * @param stdclass $data (passed by reference)
  116. * @param moodle_form $mform
  117. */
  118. function hotpot_process_formdata(stdclass &$data, $mform) {
  119. global $CFG;
  120. require_once($CFG->dirroot.'/mod/hotpot/locallib.php');
  121. if ($mform->is_add()) {
  122. $data->timecreated = time();
  123. } else {
  124. $data->timemodified = time();
  125. }
  126. // get context for this HotPot instance
  127. $context = hotpot_get_context(CONTEXT_MODULE, $data->coursemodule);
  128. $sourcefile = null;
  129. $data->sourcefile = '';
  130. $data->sourcetype = '';
  131. if ($data->sourceitemid) {
  132. $options = hotpot::sourcefile_options();
  133. file_save_draft_area_files($data->sourceitemid, $context->id, 'mod_hotpot', 'sourcefile', 0, $options);
  134. $fs = get_file_storage();
  135. $files = $fs->get_area_files($context->id, 'mod_hotpot', 'sourcefile');
  136. // do we need to remove the draft files ?
  137. // otherwise the "files" table seems to get full of "draft" records
  138. // $fs->delete_area_files($context->id, 'user', 'draft', $data->sourceitemid);
  139. foreach ($files as $hash => $file) {
  140. if ($file->get_sortorder()==1) {
  141. $data->sourcefile = $file->get_filepath().$file->get_filename();
  142. $data->sourcetype = hotpot::get_sourcetype($file);
  143. $sourcefile = $file;
  144. break;
  145. }
  146. }
  147. unset($fs, $files, $file, $hash, $options);
  148. }
  149. if (is_null($sourcefile) || $data->sourcefile=='' || $data->sourcetype=='') {
  150. // sourcefile was missing or not a recognized type - shouldn't happen !!
  151. }
  152. // process text fields that may come from source file
  153. $source = false;
  154. $textfields = array('name', 'entrytext', 'exittext');
  155. foreach($textfields as $textfield) {
  156. $textsource = $textfield.'source';
  157. if (! isset($data->$textsource)) {
  158. $data->$textsource = hotpot::TEXTSOURCE_SPECIFIC;
  159. }
  160. switch ($data->$textsource) {
  161. case hotpot::TEXTSOURCE_FILE:
  162. if ($data->sourcetype && $sourcefile && empty($source)) {
  163. $class = 'hotpot_source_'.$data->sourcetype;
  164. $source = new $class($sourcefile, $data);
  165. }
  166. $method = 'get_'.$textfield;
  167. if ($source && method_exists($source, $method)) {
  168. $data->$textfield = $source->$method();
  169. } else {
  170. $data->$textfield = '';
  171. }
  172. break;
  173. case hotpot::TEXTSOURCE_FILENAME:
  174. $data->$textfield = basename($data->sourcefile);
  175. break;
  176. case hotpot::TEXTSOURCE_FILEPATH:
  177. $data->$textfield = str_replace(array('/', '\\'), ' ', $data->sourcefile);
  178. break;
  179. case hotpot::TEXTSOURCE_SPECIFIC:
  180. default:
  181. if (isset($data->$textfield)) {
  182. $data->$textfield = trim($data->$textfield);
  183. } else {
  184. $data->$textfield = $mform->get_original_value($textfield, '');
  185. }
  186. }
  187. // default activity name is simply "HotPot"
  188. if ($textfield=='name' && $data->$textfield=='') {
  189. $data->$textfield = get_string('modulename', 'hotpot');
  190. }
  191. }
  192. // process entry/exit page settings
  193. foreach (hotpot::text_page_types() as $type) {
  194. // show page (boolean switch)
  195. $pagefield = $type.'page';
  196. if (! isset($data->$pagefield)) {
  197. $data->$pagefield = 0;
  198. }
  199. // set field names
  200. $textfield = $type.'text';
  201. $formatfield = $type.'format';
  202. $editorfield = $type.'editor';
  203. $sourcefield = $type.'textsource';
  204. $optionsfield = $type.'options';
  205. // ensure text, format and option fields are set
  206. // (these fields can't be null in the database)
  207. if (! isset($data->$textfield)) {
  208. $data->$textfield = $mform->get_original_value($textfield, '');
  209. }
  210. if (! isset($data->$formatfield)) {
  211. $data->$formatfield = $mform->get_original_value($formatfield, FORMAT_HTML);
  212. }
  213. if (! isset($data->$optionsfield)) {
  214. $data->$optionsfield = $mform->get_original_value($optionsfield, 0);
  215. }
  216. // set text and format fields
  217. if ($data->$sourcefield==hotpot::TEXTSOURCE_SPECIFIC) {
  218. // transfer wysiwyg editor text
  219. if ($itemid = $data->{$editorfield}['itemid']) {
  220. if (isset($data->{$editorfield}['text'])) {
  221. // get the text that was sent from the browser
  222. $editoroptions = hotpot::text_editors_options($context);
  223. $text = file_save_draft_area_files($itemid, $context->id, 'mod_hotpot', $type, 0, $editoroptions, $data->{$editorfield}['text']);
  224. // remove leading and trailing white space,
  225. // - empty html paragraphs (from IE)
  226. // - and blank lines (from Firefox)
  227. $text = preg_replace('/^((<p>\s*<\/p>)|(<br[^>]*>)|\s)+/is', '', $text);
  228. $text = preg_replace('/((<p>\s*<\/p>)|(<br[^>]*>)|\s)+$/is', '', $text);
  229. $data->$textfield = $text;
  230. $data->$formatfield = $data->{$editorfield}['format'];
  231. }
  232. }
  233. }
  234. // set entry/exit page options
  235. foreach (hotpot::text_page_options($type) as $name => $mask) {
  236. $optionfield = $type.'_'.$name;
  237. if ($data->$pagefield) {
  238. if (empty($data->$optionfield)) {
  239. // disable this option
  240. $data->$optionsfield = $data->$optionsfield & ~$mask;
  241. } else {
  242. // enable this option
  243. $data->$optionsfield = $data->$optionsfield | $mask;
  244. }
  245. }
  246. }
  247. // don't show exit page if no content is specified
  248. if ($type=='exit' && empty($data->$optionsfield) && empty($data->$textfield)) {
  249. $data->$pagefield = 0;
  250. }
  251. }
  252. // timelimit
  253. if ($data->timelimit==hotpot::TIME_SPECIFIC) {
  254. $data->timelimit = $data->timelimitspecific;
  255. }
  256. // delay3
  257. if ($data->delay3==hotpot::TIME_SPECIFIC) {
  258. $data->delay3 = $data->delay3specific;
  259. }
  260. // set stopbutton and stoptext
  261. if (empty($data->stopbutton_yesno)) {
  262. $data->stopbutton = hotpot::STOPBUTTON_NONE;
  263. $data->stoptext = $mform->get_original_value('stoptext', '');
  264. } else {
  265. if (! isset($data->stopbutton_type)) {
  266. $data->stopbutton_type = '';
  267. }
  268. if (! isset($data->stopbutton_text)) {
  269. $data->stopbutton_text = '';
  270. }
  271. if ($data->stopbutton_type=='specific') {
  272. $data->stopbutton = hotpot::STOPBUTTON_SPECIFIC;
  273. $data->stoptext = $data->stopbutton_text;
  274. } else {
  275. $data->stopbutton = hotpot::STOPBUTTON_LANGPACK;
  276. $data->stoptext = $data->stopbutton_type;
  277. }
  278. }
  279. // save these form settings as user preferences
  280. $preferences = array();
  281. foreach (hotpot::user_preferences_fieldnames() as $fieldname) {
  282. if (isset($data->$fieldname)) {
  283. $preferences['hotpot_'.$fieldname] = $data->$fieldname;
  284. }
  285. }
  286. set_user_preferences($preferences);
  287. }
  288. /**
  289. * Given an ID of an instance of this module,
  290. * this function will permanently delete the instance
  291. * and any data that depends on it.
  292. *
  293. * @param int $id Id of the module instance
  294. * @return boolean Success/Failure
  295. */
  296. function hotpot_delete_instance($id) {
  297. global $CFG, $DB;
  298. require_once($CFG->dirroot.'/lib/gradelib.php');
  299. // check the hotpot $id is valid
  300. if (! $hotpot = $DB->get_record('hotpot', array('id' => $id))) {
  301. return false;
  302. }
  303. // delete all associated hotpot questions
  304. $DB->delete_records('hotpot_questions', array('hotpotid' => $hotpot->id));
  305. // delete all associated hotpot attempts, details and responses
  306. if ($attempts = $DB->get_records('hotpot_attempts', array('hotpotid' => $hotpot->id), '', 'id')) {
  307. $ids = array_keys($attempts);
  308. $DB->delete_records_list('hotpot_details', 'attemptid', $ids);
  309. $DB->delete_records_list('hotpot_responses', 'attemptid', $ids);
  310. $DB->delete_records_list('hotpot_attempts', 'id', $ids);
  311. }
  312. // remove records from the hotpot cache
  313. $DB->delete_records('hotpot_cache', array('hotpotid' => $hotpot->id));
  314. // finally remove the hotpot record itself
  315. $DB->delete_records('hotpot', array('id' => $hotpot->id));
  316. // gradebook cleanup
  317. grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, null, array('deleted' => true));
  318. return true;
  319. }
  320. /**
  321. * Return a small object with summary information about what a
  322. * user has done with a given particular instance of this module
  323. * Used for user activity reports.
  324. * $return->time = the time they did it
  325. * $return->info = a short text description
  326. *
  327. * @global object $DB
  328. * @param object $course
  329. * @param object $user
  330. * @param object $mod
  331. * @param object $hotpot
  332. * @return stdclass|null
  333. */
  334. function hotpot_user_outline($course, $user, $mod, $hotpot) {
  335. global $CFG, $DB;
  336. require_once($CFG->dirroot.'/mod/hotpot/locallib.php');
  337. $conditions = array('hotpotid'=>$hotpot->id, 'userid'=>$user->id);
  338. if (! $attempts = $DB->get_records('hotpot_attempts', $conditions, "timestart ASC", 'id,score,timestart')) {
  339. return null;
  340. }
  341. $time = 0;
  342. $info = null;
  343. $scores = array();
  344. foreach ($attempts as $attempt){
  345. if ($time==0) {
  346. $time = $attempt->timestart;
  347. }
  348. $scores[] = hotpot::format_score($attempt);
  349. }
  350. if (count($scores)) {
  351. $info = get_string('score', 'hotpot').': '.implode(', ', $scores);
  352. } else {
  353. $info = get_string('noactivity', 'hotpot');
  354. }
  355. return (object)array('time'=>$time, 'info'=>$info);
  356. }
  357. /**
  358. * Print a detailed representation of what a user has done with
  359. * a given particular instance of this module, for user activity reports.
  360. *
  361. * @return string HTML
  362. */
  363. function hotpot_user_complete($course, $user, $mod, $hotpot) {
  364. $report = hotpot_user_outline($course, $user, $mod, $hotpot);
  365. if (empty($report)) {
  366. echo get_string("noactivity", 'hotpot');
  367. } else {
  368. $date = userdate($report->time, get_string('strftimerecentfull'));
  369. echo $report->info.' '.get_string('mostrecently').': '.$date;
  370. }
  371. return true;
  372. }
  373. /**
  374. * Given a course and a time, this module should find recent activity
  375. * that has occurred in hotpot activities and print it out.
  376. * Return true if there was output, or false is there was none.
  377. *
  378. * @param stdclass $course
  379. * @param bool $viewfullnames
  380. * @param int $timestart
  381. * @return boolean
  382. */
  383. function hotpot_print_recent_activity($course, $viewfullnames, $timestart) {
  384. global $CFG, $DB, $OUTPUT;
  385. $result = false;
  386. // the Moodle "logs" table contains the following fields:
  387. // time, userid, course, ip, module, cmid, action, url, info
  388. // this function utilitizes the following index on the log table
  389. // log_timcoumodact_ix : time, course, module, action
  390. // log records are added by the following function in "lib/datalib.php":
  391. // add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user=0)
  392. // log records are added by the following HotPot scripts:
  393. // (scriptname : log action)
  394. // attempt.php : attempt
  395. // index.php : index
  396. // report.php : report
  397. // review.php : review
  398. // submit.php : submit
  399. // view.php : view
  400. // all these actions have a record in the "log_display" table
  401. $select = "time>? AND course=? AND module=? AND action IN (?, ?, ?, ?, ?)";
  402. $params = array($timestart, $course->id, 'hotpot', 'add', 'update', 'view', 'attempt', 'submit');
  403. if ($logs = $DB->get_records_select('log', $select, $params, 'time ASC')) {
  404. $coursecontext = hotpot_get_context(CONTEXT_COURSE, $course->id);
  405. $viewhiddensections = has_capability('moodle/course:viewhiddensections', $coursecontext);
  406. if ($modinfo = unserialize($course->modinfo)) {
  407. $coursemoduleids = array_keys($modinfo);
  408. } else {
  409. $coursemoduleids = array();
  410. }
  411. $stats = array();
  412. foreach ($logs as $log) {
  413. $cmid = $log->cmid;
  414. if (! array_key_exists($cmid, $modinfo)) {
  415. continue; // invalid $cmid - shouldn't happen !!
  416. }
  417. if (! $viewhiddensections && ! $modinfo[$cmid]->visible) {
  418. continue; // coursemodule is hidden from user
  419. }
  420. $sortorder = array_search($cmid, $coursemoduleids);
  421. if (! array_key_exists($sortorder, $stats)) {
  422. $context = hotpot_get_context(CONTEXT_MODULE, $cmid);
  423. if (has_capability('mod/hotpot:reviewmyattempts', $context) || has_capability('mod/hotpot:reviewallattempts', $context)) {
  424. $viewreport = true;
  425. } else {
  426. $viewreport = false;
  427. }
  428. $stats[$sortorder] = (object)array(
  429. 'name' => format_string(urldecode($modinfo[$cmid]->name)),
  430. 'cmid' => $cmid, 'add'=>0, 'update'=>0, 'view'=>0, 'attempt'=>0, 'submit'=>0,
  431. 'viewreport' => $viewreport,
  432. 'users' => array()
  433. );
  434. }
  435. $action = $log->action;
  436. switch ($action) {
  437. case 'add':
  438. case 'update':
  439. // store most recent time
  440. $stats[$sortorder]->$action = $log->time;
  441. break;
  442. case 'view':
  443. case 'attempt':
  444. case 'submit':
  445. // increment counter
  446. $stats[$sortorder]->$action ++;
  447. break;
  448. }
  449. $stats[$sortorder]->users[$log->userid] = true;
  450. }
  451. $strusers = get_string('users');
  452. $stradded = get_string('added', 'hotpot');
  453. $strupdated = get_string('updated', 'hotpot');
  454. $strviews = get_string('views', 'hotpot');
  455. $strattempts = get_string('attempts', 'hotpot');
  456. $strsubmits = get_string('submits', 'hotpot');
  457. $print_headline = true;
  458. ksort($stats);
  459. foreach ($stats as $stat) {
  460. $li = array();
  461. if ($stat->add) {
  462. $li[] = $stradded.': '.userdate($stat->add);
  463. }
  464. if ($stat->update) {
  465. $li[] = $strupdated.': '.userdate($stat->update);
  466. }
  467. if ($stat->viewreport) {
  468. // link to a detailed report of recent activity for this hotpot
  469. $url = new moodle_url(
  470. '/course/recent.php',
  471. array('id'=>$course->id, 'modid'=>$stat->cmid, 'date'=>$timestart)
  472. );
  473. if ($count = count($stat->users)) {
  474. $li[] = $strusers.': '.html_writer::link($url, $count);
  475. }
  476. if ($stat->view) {
  477. $li[] = $strviews.': '.html_writer::link($url, $stat->view);
  478. }
  479. if ($stat->attempt) {
  480. $li[] = $strattempts.': '.html_writer::link($url, $stat->attempt);
  481. }
  482. if ($stat->submit) {
  483. $li[] = $strsubmits.': '.html_writer::link($url, $stat->submit);
  484. }
  485. }
  486. if (count($li)) {
  487. if ($print_headline) {
  488. $print_headline = false;
  489. echo $OUTPUT->heading(get_string('modulenameplural', 'hotpot').':', 3);
  490. }
  491. $url = new moodle_url('/mod/hotpot/view.php', array('id'=>$stat->cmid));
  492. $link = html_writer::link($url, format_string($stat->name));
  493. $text = html_writer::tag('p', $link).html_writer::alist($li);
  494. echo html_writer::tag('div', $text, array('class'=>'hotpotrecentactivity'));
  495. $result = true;
  496. }
  497. }
  498. }
  499. return $result;
  500. }
  501. /**
  502. * Returns all activity in course hotpots since a given time
  503. * This function returns activity for all hotpots since a given time.
  504. * It is initiated from the "Full report of recent activity" link in the "Recent Activity" block.
  505. * Using the "Advanced Search" page (cousre/recent.php?id=99&advancedfilter=1),
  506. * results may be restricted to a particular course module, user or group
  507. *
  508. * This function is called from: {@link course/recent.php}
  509. *
  510. * @param array(object) $activities sequentially indexed array of course module objects
  511. * @param integer $index length of the $activities array
  512. * @param integer $timestart start date, as a UNIX date
  513. * @param integer $courseid id in the "course" table
  514. * @param integer $coursemoduleid id in the "course_modules" table
  515. * @param integer $userid id in the "users" table (default = 0)
  516. * @param integer $groupid id in the "groups" table (default = 0)
  517. * @return void adds items into $activities and increments $index
  518. * for each hotpot attempt, an $activity object is appended
  519. * to the $activities array and the $index is incremented
  520. * $activity->type : module type (always "hotpot")
  521. * $activity->defaultindex : index of this object in the $activities array
  522. * $activity->instance : id in the "hotpot" table;
  523. * $activity->name : name of this hotpot
  524. * $activity->section : section number in which this hotpot appears in the course
  525. * $activity->content : array(object) containing information about hotpot attempts to be printed by {@link print_recent_mod_activity()}
  526. * $activity->content->attemptid : id in the "hotpot_quiz_attempts" table
  527. * $activity->content->attempt : the number of this attempt at this quiz by this user
  528. * $activity->content->score : the score for this attempt
  529. * $activity->content->timestart : the server time at which this attempt started
  530. * $activity->content->timefinish : the server time at which this attempt finished
  531. * $activity->user : object containing user information
  532. * $activity->user->userid : id in the "user" table
  533. * $activity->user->fullname : the full name of the user (see {@link lib/moodlelib.php}::{@link fullname()})
  534. * $activity->user->picture : $record->picture;
  535. * $activity->timestamp : the time that the content was recorded in the database
  536. */
  537. function hotpot_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $coursemoduleid=0, $userid=0, $groupid=0) {
  538. global $CFG, $DB, $USER;
  539. if (! $course = $DB->get_record('course', array('id'=>$courseid))) {
  540. return; // invalid course id - shouldn't happen !!
  541. }
  542. if (! $modinfo = unserialize($course->modinfo)) {
  543. return; // no activity mods
  544. }
  545. // CONTRIB-4025 don't allow students to see each other's scores
  546. $coursecontext = hotpot_get_context(CONTEXT_COURSE, $courseid);
  547. if (! has_capability('mod/hotpot:reviewmyattempts', $coursecontext)) {
  548. return; // can't view recent activity
  549. }
  550. if (! has_capability('mod/hotpot:reviewallattempts', $coursecontext)) {
  551. $userid = $USER->id; // force this user only
  552. }
  553. $hotpots = array(); // hotpotid => cmid
  554. foreach (array_keys($modinfo) as $cmid) {
  555. if ($modinfo[$cmid]->mod=='hotpot' && ($coursemoduleid==0 || $coursemoduleid==$cmid)) {
  556. // save mapping from hotpotid => coursemoduleid
  557. $hotpots[$modinfo[$cmid]->id] = $cmid;
  558. // initialize array of users who have recently attempted this HotPot
  559. $modinfo[$cmid]->users = array();
  560. } else {
  561. // we are not interested in this mod
  562. unset($modinfo[$cmid]);
  563. }
  564. }
  565. if (empty($hotpots)) {
  566. return; // no hotpots
  567. }
  568. list($filter, $params) = $DB->get_in_or_equal(array_keys($hotpots));
  569. $duration = '(ha.timemodified - ha.timestart) AS duration';
  570. $select = 'ha.*, '.$duration.', u.firstname, u.lastname, u.picture, u.imagealt, u.email';
  571. $from = "{hotpot_attempts} ha, {user} u";
  572. $where = "ha.hotpotid $filter AND ha.userid=u.id";
  573. $orderby = 'ha.userid, ha.attempt';
  574. if ($groupid) {
  575. // restrict search to a users from a particular group
  576. $from .= ', {groups_members} gm';
  577. $where .= ' AND ha.userid=gm.userid AND gm.id=?';
  578. $params[] = $groupid;
  579. }
  580. if ($userid) {
  581. // restrict search to a single user
  582. $where .= ' AND ha.userid=?';
  583. $params[] = $userid;
  584. }
  585. $where .= ' AND ha.timemodified>?';
  586. $params[] = $timestart;
  587. if (! $attempts = $DB->get_records_sql("SELECT $select FROM $from WHERE $where ORDER BY $orderby", $params)) {
  588. return; // no recent attempts at these hotpots
  589. }
  590. foreach (array_keys($attempts) as $attemptid) {
  591. $attempt = &$attempts[$attemptid];
  592. if (! array_key_exists($attempt->hotpotid, $hotpots)) {
  593. continue; // invalid hotpotid - shouldn't happen !!
  594. }
  595. $cmid = $hotpots[$attempt->hotpotid];
  596. if (! array_key_exists($cmid, $modinfo)) {
  597. continue; // invalid cmid - shouldn't happen !!
  598. }
  599. $mod = &$modinfo[$cmid];
  600. $userid = $attempt->userid;
  601. if (! array_key_exists($userid, $mod->users)) {
  602. $mod->users[$userid] = (object)array(
  603. 'id' => $userid,
  604. 'userid' => $userid,
  605. 'firstname' => $attempt->firstname,
  606. 'lastname' => $attempt->lastname,
  607. 'fullname' => fullname($attempt),
  608. 'picture' => $attempt->picture,
  609. 'imagealt' => $attempt->imagealt,
  610. 'email' => $attempt->email,
  611. 'attempts' => array()
  612. );
  613. }
  614. // add this attempt by this user at this course module
  615. $mod->users[$userid]->attempts[$attempt->attempt] = &$attempt;
  616. }
  617. foreach (array_keys($modinfo) as $cmid) {
  618. $mod = &$modinfo[$cmid];
  619. if (empty($mod->users)) {
  620. continue;
  621. }
  622. // add an activity object for each user's attempts at this hotpot
  623. foreach (array_keys($mod->users) as $userid) {
  624. $user = &$mod->users[$userid];
  625. // get index of last (=most recent) attempt
  626. $max_unumber = max(array_keys($user->attempts));
  627. $activities[$index++] = (object)array(
  628. 'type' => 'hotpot',
  629. 'cmid' => $cmid,
  630. 'name' => format_string(urldecode($mod->name)),
  631. 'user' => (object)array(
  632. 'id' => $user->id,
  633. 'userid' => $user->userid,
  634. 'firstname' => $user->firstname,
  635. 'lastname' => $user->lastname,
  636. 'fullname' => $user->fullname,
  637. 'picture' => $user->picture,
  638. 'imagealt' => $user->imagealt,
  639. 'email' => $user->email
  640. ),
  641. 'attempts' => $user->attempts,
  642. 'timestamp' => $user->attempts[$max_unumber]->timemodified
  643. );
  644. }
  645. }
  646. }
  647. /**
  648. * Print single activity item prepared by {@see hotpot_get_recent_mod_activity()}
  649. *
  650. * This function is called from: {@link course/recent.php}
  651. *
  652. * @param object $activity an object created by {@link get_recent_mod_activity()}
  653. * @param integer $courseid id in the "course" table
  654. * @param boolean $detail
  655. * true : print a link to the hotpot activity
  656. * false : do no print a link to the hotpot activity
  657. * @param xxx $modnames
  658. * @param xxx $viewfullnames
  659. * @return no return value is required
  660. */
  661. function hotpot_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
  662. global $CFG, $OUTPUT;
  663. require_once($CFG->dirroot.'/mod/hotpot/locallib.php');
  664. static $dateformat = null;
  665. if (is_null($dateformat)) {
  666. $dateformat = get_string('strftimerecentfull');
  667. }
  668. $table = new html_table();
  669. $table->cellpadding = 3;
  670. $table->cellspacing = 0;
  671. if ($detail) {
  672. $row = new html_table_row();
  673. $cell = new html_table_cell('&nbsp;', array('width'=>15));
  674. $row->cells[] = $cell;
  675. // activity icon and link to activity
  676. $src = $OUTPUT->pix_url('icon', $activity->type);
  677. $img = html_writer::tag('img', array('src'=>$src, 'class'=>'icon', $alt=>$activity->name));
  678. // link to activity
  679. $href = new moodle_url('/mod/hotpot/view.php', array('id' => $activity->cmid));
  680. $link = html_writer::link($href, $activity->name);
  681. $cell = new html_table_cell("$img $link");
  682. $cell->colspan = 6;
  683. $row->cells[] = $cell;
  684. $table->data[] = new html_table_row(array(
  685. new html_table_cell('&nbsp;', array('width'=>15)),
  686. new html_table_cell("$img $link")
  687. ));
  688. $table->data[] = $row;
  689. }
  690. $row = new html_table_row();
  691. // set rowspan to (number of attempts) + 1
  692. $rowspan = count($activity->attempts) + 1;
  693. $cell = new html_table_cell('&nbsp;', array('width'=>15));
  694. $cell->rowspan = $rowspan;
  695. $row->cells[] = $cell;
  696. $picture = $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
  697. $cell = new html_table_cell($picture, array('width'=>35, 'valign'=>'top', 'class'=>'forumpostpicture'));
  698. $cell->rowspan = $rowspan;
  699. $row->cells[] = $cell;
  700. $href = new moodle_url('/user/view.php', array('id'=>$activity->user->userid, 'course'=>$courseid));
  701. $cell = new html_table_cell(html_writer::link($href, $activity->user->fullname));
  702. $cell->colspan = 5;
  703. $row->cells[] = $cell;
  704. $table->data[] = $row;
  705. foreach ($activity->attempts as $attempt) {
  706. if ($attempt->duration) {
  707. $duration = '('.hotpot::format_time($attempt->duration).')';
  708. } else {
  709. $duration = '&nbsp;';
  710. }
  711. $href = new moodle_url('/mod/hotpot/review.php', array('id'=>$attempt->id));
  712. $link = html_writer::link($href, userdate($attempt->timemodified, $dateformat));
  713. $table->data[] = new html_table_row(array(
  714. new html_table_cell($attempt->attempt),
  715. new html_table_cell($attempt->score.'%'),
  716. new html_table_cell(hotpot::format_status($attempt->status, true)),
  717. new html_table_cell($link),
  718. new html_table_cell($duration)
  719. ));
  720. }
  721. echo html_writer::table($table);
  722. }
  723. /*
  724. * This function defines what log actions will be selected from the Moodle logs
  725. * and displayed for course -> report -> activity module -> HotPOt -> View OR All actions
  726. *
  727. * This function is called from: {@link course/report/participation/index.php}
  728. * @return array(string) of text strings used to log QuizPort view actions
  729. */
  730. function hotpot_get_view_actions() {
  731. return array('view', 'viewindex', 'report', 'review');
  732. }
  733. /*
  734. * This function defines what log actions will be selected from the Moodle logs
  735. * and displayed for course -> report -> activity module -> Hot Potatoes Quiz -> Post OR All actions
  736. *
  737. * This function is called from: {@link course/report/participation/index.php}
  738. * @return array(string) of text strings used to log QuizPort post actions
  739. */
  740. function hotpot_get_post_actions() {
  741. return array('submit');
  742. }
  743. /**
  744. * Function to be run periodically according to the moodle cron
  745. * This function searches for things that need to be done, such
  746. * as sending out mail, toggling flags etc ...
  747. *
  748. * @return boolean
  749. * @todo Finish documenting this function
  750. **/
  751. function hotpot_cron () {
  752. return true;
  753. }
  754. /**
  755. * Returns an array of user ids who are participanting in this hotpot
  756. *
  757. * @param int $hotpotid ID of an instance of this module
  758. * @return array of user ids, empty if there are no participants
  759. */
  760. function hotpot_get_participants($hotpotid) {
  761. global $DB;
  762. $select = 'DISTINCT u.id, u.id';
  763. $from = '{user} u, {hotpot_attempts} a';
  764. $where = 'u.id=a.userid AND a.hotpot=?';
  765. $params = array($hotpotid);
  766. return $DB->get_records_sql("SELECT $select FROM $from WHERE $where", $params);
  767. }
  768. /**
  769. * Is a given scale used by the instance of hotpot?
  770. *
  771. * The function asks all installed grading strategy subplugins. The hotpot
  772. * core itself does not use scales. Both grade for submission and grade for
  773. * assessments do not use scales.
  774. *
  775. * @param int $hotpotid id of hotpot instance
  776. * @param int $scaleid id of the scale to check
  777. * @return bool
  778. */
  779. function hotpot_scale_used($hotpotid, $scaleid) {
  780. return false;
  781. }
  782. /**
  783. * Is a given scale used by any instance of hotpot?
  784. *
  785. * The function asks all installed grading strategy subplugins. The hotpot
  786. * core itself does not use scales. Both grade for submission and grade for
  787. * assessments do not use scales.
  788. *
  789. * @param int $scaleid id of the scale to check
  790. * @return bool
  791. */
  792. function hotpot_scale_used_anywhere($scaleid) {
  793. return false;
  794. }
  795. /**
  796. * Returns all other caps used in the module
  797. *
  798. * @return array
  799. */
  800. function hotpot_get_extra_capabilities() {
  801. return array('moodle/site:accessallgroups');
  802. }
  803. ////////////////////////////////////////////////////////////////////////////////
  804. // Gradebook API //
  805. ////////////////////////////////////////////////////////////////////////////////
  806. /**
  807. * Creates or updates grade items for the given hotpot instance
  808. *
  809. * Needed by grade_update_mod_grades() in lib/gradelib.php.
  810. * Also used by {@link hotpot_update_grades()}.
  811. *
  812. * @param stdclass $hotpot instance object with extra cmidnumber and modname property
  813. * @return void
  814. */
  815. function hotpot_grade_item_update($hotpot, $grades=null) {
  816. global $CFG;
  817. require_once($CFG->dirroot.'/lib/gradelib.php');
  818. // sanity check on $hotpot->id
  819. if (! isset($hotpot->id)) {
  820. return;
  821. }
  822. $params = array(
  823. 'itemname' => $hotpot->name
  824. );
  825. if ($grades==='reset') {
  826. $params['reset'] = true;
  827. $grades = null;
  828. }
  829. if (property_exists($hotpot, 'cmidnumber')) {
  830. //cmidnumber may not be always present
  831. $params['idnumber'] = $hotpot->cmidnumber;
  832. }
  833. if ($hotpot->gradeweighting > 0) {
  834. $params['gradetype'] = GRADE_TYPE_VALUE;
  835. $params['grademax'] = $hotpot->gradeweighting;
  836. $params['grademin'] = 0;
  837. } else {
  838. $params['gradetype'] = GRADE_TYPE_NONE;
  839. }
  840. return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, $grades, $params);
  841. }
  842. /**
  843. * Update hotpot grades in the gradebook
  844. *
  845. * Needed by grade_update_mod_grades() in lib/gradelib.php
  846. *
  847. * @param stdclass $hotpot instance object with extra cmidnumber and modname property
  848. * @param int $userid update grade of specific user only, 0 means all participants
  849. * @return void
  850. */
  851. function hotpot_update_grades($hotpot, $userid=0, $nullifnone=true) {
  852. global $CFG, $DB;
  853. // get hotpot object
  854. require_once($CFG->dirroot.'/mod/hotpot/locallib.php');
  855. // sanity check on $hotpot->id
  856. if (! isset($hotpot->id)) {
  857. return;
  858. }
  859. if ($hotpot->grademethod==hotpot::GRADEMETHOD_AVERAGE || $hotpot->gradeweighting<100) {
  860. $precision = 1;
  861. } else {
  862. $precision = 0;
  863. }
  864. $weighting = $hotpot->gradeweighting / 100;
  865. // set the SQL string to determine the $grade
  866. switch ($hotpot->grademethod) {
  867. case hotpot::GRADEMETHOD_HIGHEST:
  868. $gradefield = "ROUND(MAX(score) * $weighting, $precision) AS grade";
  869. break;
  870. case hotpot::GRADEMETHOD_AVERAGE:
  871. // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
  872. $gradefield = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
  873. break;
  874. case hotpot::GRADEMETHOD_FIRST:
  875. $gradefield = "ROUND(score * $weighting, $precision)";
  876. $gradefield = $DB->sql_concat('timestart', "'_'", $gradefield);
  877. $gradefield = "MIN($gradefield) AS grade";
  878. break;
  879. case hotpot::GRADEMETHOD_LAST:
  880. $gradefield = "ROUND(score * $weighting, $precision)";
  881. $gradefield = $DB->sql_concat('timestart', "'_'", $gradefield);
  882. $gradefield = "MAX($gradefield) AS grade";
  883. break;
  884. default:
  885. return false; // shouldn't happen !!
  886. }
  887. $select = 'timefinish>0 AND hotpotid= ?';
  888. $params = array($hotpot->id);
  889. if ($userid) {
  890. $select .= ' AND userid = ?';
  891. $params[] = $userid;
  892. }
  893. $sql = "SELECT userid, $gradefield FROM {hotpot_attempts} WHERE $select GROUP BY userid";
  894. $grades = array();
  895. if ($hotpotgrades = $DB->get_records_sql_menu($sql, $params)) {
  896. foreach ($hotpotgrades as $hotpotuserid => $hotpotgrade) {
  897. if ($hotpot->grademethod==hotpot::GRADEMETHOD_FIRST || $hotpot->grademethod==hotpot::GRADEMETHOD_LAST) {
  898. // remove left hand characters in $gradefield (up to and including the underscore)
  899. $pos = strpos($hotpotgrade, '_') + 1;
  900. $hotpotgrade = substr($hotpotgrade, $pos);
  901. }
  902. $grades[$hotpotuserid] = (object)array('userid'=>$hotpotuserid, 'rawgrade'=>$hotpotgrade);
  903. }
  904. }
  905. if (count($grades)) {
  906. hotpot_grade_item_update($hotpot, $grades);
  907. } else if ($userid && $nullifnone) {
  908. // no grades for this user, but we must force the creation of a "null" grade record
  909. hotpot_grade_item_update($hotpot, (object)array('userid'=>$userid, 'rawgrade'=>null));
  910. } else {
  911. // no grades and no userid
  912. hotpot_grade_item_update($hotpot);
  913. }
  914. }
  915. ////////////////////////////////////////////////////////////////////////////////
  916. // File API //
  917. ////////////////////////////////////////////////////////////////////////////////
  918. /**
  919. * Returns the lists of all browsable file areas within the given module context
  920. *
  921. * The file area hotpot_intro for the activity introduction field is added automatically
  922. * by {@link file_browser::get_file_info_context_module()}
  923. *
  924. * @param stdclass $course
  925. * @param stdclass $cm
  926. * @param stdclass $context
  927. * @return array of [(string)filearea] => (string)description
  928. */
  929. function hotpot_get_file_areas($course, $cm, $context) {
  930. return array(
  931. 'entry' => get_string('entrytext', 'hotpot'),
  932. 'exit' => get_string('exittext', 'hotpot'),
  933. 'sourcefile' => get_string('sourcefile', 'hotpot')
  934. );
  935. }
  936. /**
  937. * Serves the plugin files from the specified $filearea
  938. *
  939. * @package mod_hotpot
  940. * @category files
  941. * @param stdClass $course course object
  942. * @param stdClass $cm course module object
  943. * @param stdClass $context context object
  944. * @param string $filearea file area
  945. * @param array $args extra arguments
  946. * @param bool $forcedownload whether or not force download
  947. * @param array $options additional options affecting the file serving
  948. * @return bool false if file not found, does not return if found - just send the file
  949. */
  950. function hotpot_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, $options=array()) {
  951. global $CFG;
  952. require_course_login($course, true, $cm);
  953. switch ($filearea) {
  954. case 'entry': $capability = 'mod/hotpot:view'; break;
  955. case 'exit': $capability = 'mod/hotpot:attempt'; break;
  956. case 'sourcefile': $capability = 'mod/hotpot:attempt'; break;
  957. default: send_file_not_found(); // invalid $filearea !!
  958. }
  959. require_capability($capability, $context);
  960. $fs = get_file_storage();
  961. $component = 'mod_hotpot';
  962. $filename = array_pop($args);
  963. $filepath = $args ? '/'.implode('/', $args).'/' : '/';
  964. // Note: $lifetime is the frequency at which files are synched
  965. if (isset($CFG->filelifetime)) {
  966. $lifetime = $CFG->filelifetime;
  967. } else {
  968. $lifetime = DAYSECS; // DAYSECS = 86400 secs = 24 hours
  969. }
  970. $filter = 0; // don't apply filters
  971. if ($file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename)) {
  972. // file found - this is what we expect to happen
  973. send_stored_file($file, $lifetime, $filter, $forcedownload, $options);
  974. }
  975. /////////////////////////////////////////////////////////////
  976. // If we get to this point, it is because the requested file
  977. // is not where is was supposed to be, so we will search for
  978. // it in some other likely locations.
  979. // If we find it, we will copy it across to where it is
  980. // supposed to be, so it can be found more quickly next time
  981. /////////////////////////////////////////////////////////////
  982. $file_record = array(
  983. 'contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea,
  984. 'sortorder'=>0, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename
  985. );
  986. // search in external directory
  987. if ($file = hotpot_pluginfile_externalfile($context, $component, $filearea, $filepath, $filename, $file_record)) {
  988. send_stored_file($file, $lifetime, $filter, $forcedownload, $options);
  989. }
  990. // search course legacy files
  991. $coursecontext = hotpot_get_context(CONTEXT_COURSE, $course->id);
  992. if ($file = $fs->get_file($coursecontext->id, 'course', 'legacy', 0, $filepath, $filename)) {
  993. if ($file = $fs->create_file_from_storedfile($file_record, $file)) {
  994. //send_stored_file($file, $lifetime, 0);
  995. send_stored_file($file, $lifetime, $filter, $forcedownload, $options);
  996. }
  997. }
  998. // search local file system
  999. $oldfilepath = $CFG->dataroot.'/'.$course->id.$filepath.$filename;
  1000. if (file_exists($oldfilepath)) {
  1001. if ($file = $fs->create_file_from_pathname($file_record, $oldfilepath)) {
  1002. send_stored_file($file, $lifetime, 0);
  1003. }
  1004. }
  1005. // search other fileareas for this HotPot
  1006. $hotpot_fileareas = hotpot_get_file_areas($course, $cm, $context);
  1007. $hotpot_fileareas = array_keys($hotpot_fileareas);
  1008. foreach($hotpot_fileareas as $hotpot_filearea) {
  1009. if ($hotpot_filearea==$filearea) {
  1010. continue; // we have already checked this filearea
  1011. }
  1012. if ($file = $fs->get_file($context->id, $component, $hotpot_filearea, 0, $filepath, $filename)) {
  1013. if ($file = $fs->create_file_from_storedfile($file_record, $file)) {
  1014. send_stored_file($file, $lifetime, 0);
  1015. }
  1016. }
  1017. }
  1018. // file not found :-(
  1019. send_file_not_found();
  1020. }
  1021. /**
  1022. * Gets main file in a file area
  1023. *
  1024. * if the main file is a link from an external repository
  1025. * look for the target file in the main file's repository
  1026. * Note: this functionality only exists in Moodle 2.3+
  1027. *
  1028. * @param stdclass $context
  1029. * @param string $component 'mod_hotpot'
  1030. * @param string $filearea 'sourcefile', 'entrytext' or 'exittext'
  1031. * @param string $filepath despite the name, this is a dir path with leading and trailing "/"
  1032. * @param string $filename
  1033. * @param array $file_record
  1034. * @return stdclass if external file found, false otherwise
  1035. */
  1036. function hotpot_pluginfile_externalfile($context, $component, $filearea, $filepath, $filename, $file_record) {
  1037. // get file storage
  1038. $fs = get_file_storage();
  1039. // get main file for this $component/$filearea
  1040. // typically this will be the HotPot quiz file
  1041. $mainfile = hotpot_pluginfile_mainfile($context, $component, $filearea);
  1042. // get repository - cautiously :-)
  1043. if (! $mainfile) {
  1044. return false; // no main file - shouldn't happen !!
  1045. }
  1046. if (! method_exists($mainfile, 'get_repository_id')) {
  1047. return false; // no file linking in Moodle 2.0 - 2.2
  1048. }
  1049. if (! $repositoryid = $mainfile->get_repository_id()) {
  1050. return false; // $mainfile is not from an external repository
  1051. }
  1052. if (! $repository = repository::get_repository_by_id($repositoryid, $context)) {
  1053. return false; // $repository is not accessible in this context - shouldn't happen !!
  1054. }
  1055. // get repository type
  1056. switch (true) {
  1057. case isset($repository->options['type']):
  1058. $type = $repository->options['type'];
  1059. break;
  1060. case isset($repository->instance->typeid):
  1061. $type = repository::get_type_by_id($repository->instance->typeid);
  1062. $type = $type->get_typename();
  1063. break;
  1064. default:
  1065. $type = ''; // shouldn't happen !!
  1066. }
  1067. // set paths (within repository) to required file
  1068. // how we do this depends on the repository $typename
  1069. // "filesystem" path is in plain text, others are encoded
  1070. $mainreference = $mainfile->get_reference();
  1071. switch ($type) {
  1072. case 'filesystem':
  1073. $maindirname = dirname($mainreference);
  1074. $encodepath = false;
  1075. break;
  1076. case 'user':
  1077. case 'coursefiles':
  1078. $params = file_storage::unpack_reference($mainreference, true);
  1079. $maindirname = $params['filepath'];
  1080. $encodepath = true;
  1081. break;
  1082. default:
  1083. echo 'unknown repository type in hotpot_pluginfile_externalfile(): '.$type;
  1084. die;
  1085. }
  1086. // remove leading and trailing "/" from dir names
  1087. $maindirname = trim($maindirname, '/');
  1088. $dirname = trim($filepath, '/');
  1089. // assume path to target dir is same as path to main dir
  1090. $path = explode('/', $maindirname);
  1091. // traverse back up folder hierarchy if necessary
  1092. $count = count(explode('/', $dirname));
  1093. array_splice($path, -$count);
  1094. // reconstruct expected dir path for source file
  1095. if ($dirname) {
  1096. $path[] = $dirname;
  1097. }
  1098. $source = $path;
  1099. $source[] = $filename;
  1100. $source = implode('/', $source);
  1101. $path = implode('/', $path);
  1102. // filepaths in the repository to search for the file
  1103. $paths = array();
  1104. // add to the list of possible paths
  1105. $paths[$path] = $source;
  1106. if ($dirname) {
  1107. $paths[$dirname] = $dirname.'/'.$filename;
  1108. }
  1109. if ($maindirname) {
  1110. $paths[$maindirname] = $maindirname.'/'.$filename;
  1111. }
  1112. if ($maindirname && $dirname) {
  1113. $paths[$maindirname.'/'.$dirname] = $maindirname.'/'.$dirname.'/'.$filename;
  1114. $paths[$dirname.'/'.$maindirname] = $dirname.'/'.$maindirname.'/'.$filename;
  1115. }
  1116. // add leading and trailing "/" to dir names
  1117. $dirname = ($dirname=='' ? '/' : '/'.$dirname.'/');
  1118. $maindirname = ($maindirname=='' ? '/' : '/'.$maindirname.'/');
  1119. // locate $dirname within $maindirname
  1120. // typically it will be absent or occur just once,
  1121. // but it could possibly occur several times
  1122. $search = '/'.preg_quote($dirname, '/').'/i';
  1123. if (preg_match_all($search, $maindirname, $matches, PREG_OFFSET_CAPTURE)) {
  1124. $i_max = count($matches[0]);
  1125. for ($i=0; $i<$i_max; $i++) {
  1126. list($match, $start) = $matches[0][$i];
  1127. $path = substr($maindirname, 0, $start).$match;
  1128. $path = trim($path, '/'); // e.g. hp6.2/html_files
  1129. $paths[$path] = $path.'/'.$filename;
  1130. }
  1131. }
  1132. // setup $params for path encoding, if necessary
  1133. $params = array();
  1134. if ($encodepath) {
  1135. $listing = $repository->get_listing();
  1136. if (isset($listing['list'][0]['path'])) {
  1137. $params = file_storage::unpack_reference($listing['list'][0]['path'], true);
  1138. }
  1139. }
  1140. foreach ($paths as $path => $source) {
  1141. if (! hotpot_pluginfile_dirpath_exists($path, $repository, $encodepath, $params)) {
  1142. continue;
  1143. }
  1144. if ($encodepath) {
  1145. $params['filepath'] = '/'.$path.($path=='' ? '' : '/');
  1146. $params['filename'] = '.'; // "." signifies a directory
  1147. $path = file_storage::pack_reference($params);
  1148. }
  1149. $listing = $repository->get_listing($path);
  1150. foreach ($listing['list'] as $file) {
  1151. if (empty($file['source'])) {
  1152. continue; // a directory - shouldn't happen !!
  1153. }
  1154. if ($encodepath) {
  1155. $file['source'] = file_storage::unpack_reference($file['source']);
  1156. $file['source'] = trim($file['source']['filepath'], '/').'/'.$file['source']['filename'];
  1157. }
  1158. if ($file['source']==$source) {
  1159. if ($encodepath) {
  1160. $params['filename'] = $filename;
  1161. $source = file_storage::pack_reference($params);
  1162. }
  1163. if ($file = $fs->create_file_from_reference($file_record, $repositoryid, $source)) {
  1164. return $file;
  1165. }
  1166. break; // couldn't create file, so give up and try a different $path
  1167. }
  1168. }
  1169. }
  1170. // external file not found (or found but not created)
  1171. return false;
  1172. }
  1173. /**
  1174. * Determine if dir path exists or not in repository
  1175. *
  1176. * @param string $dirpath
  1177. * @param stdclass $repository
  1178. * @param boolean $encodepath
  1179. * @param array $params
  1180. * @return boolean true if dir path exi…

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