MDL-23486 fixed regression, sorrrrry
[moodle.git] / mod / lesson / locallib.php
CommitLineData
87f83794 1<?php
2
0a4abb73
SH
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
5491947a 18/**
19 * Local library file for Lesson. These are non-standard functions that are used
20 * only by Lesson.
21 *
0a4abb73
SH
22 * @package lesson
23 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
5491947a 25 **/
4b55d2af 26
0a4abb73 27/** Make sure this isn't being directly accessed */
1e7f8ea2 28defined('MOODLE_INTERNAL') || die();
0a4abb73
SH
29
30/** Include the files that are required by this module */
1e7f8ea2 31require_once($CFG->dirroot.'/course/moodleform_mod.php');
0a4abb73
SH
32require_once($CFG->dirroot . '/mod/lesson/lib.php');
33
34/** Next page -> any page not seen before */
35define("LESSON_UNSEENPAGE", 1);
36/** Next page -> any page not answered correctly */
37define("LESSON_UNANSWEREDPAGE", 2);
38/** Jump to Next Page */
39define("LESSON_NEXTPAGE", -1);
40/** End of Lesson */
41define("LESSON_EOL", -9);
42/** Jump to an unseen page within a branch and end of branch or end of lesson */
43define("LESSON_UNSEENBRANCHPAGE", -50);
44/** Jump to Previous Page */
45define("LESSON_PREVIOUSPAGE", -40);
46/** Jump to a random page within a branch and end of branch or end of lesson */
47define("LESSON_RANDOMPAGE", -60);
48/** Jump to a random Branch */
49define("LESSON_RANDOMBRANCH", -70);
50/** Cluster Jump */
51define("LESSON_CLUSTERJUMP", -80);
52/** Undefined */
53define("LESSON_UNDEFINED", -99);
5e7856af 54
1e7f8ea2
PS
55/** LESSON_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */
56define("LESSON_MAX_EVENT_LENGTH", "432000");
57
58
5e7856af 59//////////////////////////////////////////////////////////////////////////////////////
86342d63 60/// Any other lesson functions go here. Each of them must have a name that
5e7856af 61/// starts with lesson_
62
4b55d2af 63/**
86342d63 64 * Checks to see if a LESSON_CLUSTERJUMP or
4b55d2af 65 * a LESSON_UNSEENBRANCHPAGE is used in a lesson.
66 *
86342d63 67 * This function is only executed when a teacher is
4b55d2af 68 * checking the navigation for a lesson.
69 *
06469639 70 * @param int $lesson Id of the lesson that is to be checked.
4b55d2af 71 * @return boolean True or false.
72 **/
06469639 73function lesson_display_teacher_warning($lesson) {
646fc290 74 global $DB;
86342d63 75
ac8e16be 76 // get all of the lesson answers
0a4abb73 77 $params = array ("lessonid" => $lesson->id);
646fc290 78 if (!$lessonanswers = $DB->get_records_select("lesson_answers", "lessonid = :lessonid", $params)) {
ac8e16be 79 // no answers, then not useing cluster or unseen
80 return false;
81 }
82 // just check for the first one that fulfills the requirements
83 foreach ($lessonanswers as $lessonanswer) {
84 if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) {
85 return true;
86 }
87 }
86342d63 88
ac8e16be 89 // if no answers use either of the two jumps
90 return false;
5e7856af 91}
92
4b55d2af 93/**
94 * Interprets the LESSON_UNSEENBRANCHPAGE jump.
86342d63 95 *
4b55d2af 96 * will return the pageid of a random unseen page that is within a branch
97 *
0a4abb73 98 * @param lesson $lesson
f521f98a 99 * @param int $userid Id of the user.
4b55d2af 100 * @param int $pageid Id of the page from which we are jumping.
101 * @return int Id of the next page.
4b55d2af 102 **/
5e7856af 103function lesson_unseen_question_jump($lesson, $user, $pageid) {
646fc290 104 global $DB;
86342d63 105
ac8e16be 106 // get the number of retakes
0a4abb73 107 if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$user))) {
ac8e16be 108 $retakes = 0;
109 }
110
111 // get all the lesson_attempts aka what the user has seen
0a4abb73 112 if ($viewedpages = $DB->get_records("lesson_attempts", array("lessonid"=>$lesson->id, "userid"=>$user, "retry"=>$retakes), "timeseen DESC")) {
ac8e16be 113 foreach($viewedpages as $viewed) {
114 $seenpages[] = $viewed->pageid;
115 }
116 } else {
117 $seenpages = array();
118 }
119
120 // get the lesson pages
0a4abb73 121 $lessonpages = $lesson->load_all_pages();
86342d63 122
ac8e16be 123 if ($pageid == LESSON_UNSEENBRANCHPAGE) { // this only happens when a student leaves in the middle of an unseen question within a branch series
124 $pageid = $seenpages[0]; // just change the pageid to the last page viewed inside the branch table
125 }
126
127 // go up the pages till branch table
128 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
0a4abb73 129 if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) {
ac8e16be 130 break;
131 }
132 $pageid = $lessonpages[$pageid]->prevpageid;
133 }
86342d63 134
0a4abb73 135 $pagesinbranch = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH));
86342d63 136
ac8e16be 137 // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array
138 $unseen = array();
86342d63 139 foreach($pagesinbranch as $page) {
ac8e16be 140 if (!in_array($page->id, $seenpages)) {
141 $unseen[] = $page->id;
142 }
143 }
144
145 if(count($unseen) == 0) {
146 if(isset($pagesinbranch)) {
147 $temp = end($pagesinbranch);
148 $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL
149 } else {
150 // there are no pages inside the branch, so return the next page
151 $nextpage = $lessonpages[$pageid]->nextpageid;
152 }
153 if ($nextpage == 0) {
154 return LESSON_EOL;
155 } else {
156 return $nextpage;
157 }
158 } else {
159 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
160 }
5e7856af 161}
162
4b55d2af 163/**
164 * Handles the unseen branch table jump.
165 *
0a4abb73 166 * @param lesson $lesson
f521f98a 167 * @param int $userid User id.
4b55d2af 168 * @return int Will return the page id of a branch table or end of lesson
4b55d2af 169 **/
0a4abb73 170function lesson_unseen_branch_jump($lesson, $userid) {
646fc290 171 global $DB;
86342d63 172
0a4abb73 173 if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$userid))) {
ac8e16be 174 $retakes = 0;
175 }
176
0a4abb73 177 $params = array ("lessonid" => $lesson->id, "userid" => $userid, "retry" => $retakes);
646fc290 178 if (!$seenbranches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid AND retry = :retry", $params,
ac8e16be 179 "timeseen DESC")) {
86f93345 180 print_error('cannotfindrecords', 'lesson');
ac8e16be 181 }
182
183 // get the lesson pages
0a4abb73 184 $lessonpages = $lesson->load_all_pages();
86342d63 185
ff85f902 186 // this loads all the viewed branch tables into $seen until it finds the branch table with the flag
ac8e16be 187 // which is the branch table that starts the unseenbranch function
86342d63 188 $seen = array();
ac8e16be 189 foreach ($seenbranches as $seenbranch) {
190 if (!$seenbranch->flag) {
191 $seen[$seenbranch->pageid] = $seenbranch->pageid;
192 } else {
193 $start = $seenbranch->pageid;
194 break;
195 }
196 }
197 // this function searches through the lesson pages to find all the branch tables
198 // that follow the flagged branch table
199 $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table
200 while ($pageid != 0) { // grab all of the branch table till eol
0a4abb73 201 if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) {
ac8e16be 202 $branchtables[] = $lessonpages[$pageid]->id;
203 }
204 $pageid = $lessonpages[$pageid]->nextpageid;
205 }
206 $unseen = array();
207 foreach ($branchtables as $branchtable) {
208 // load all of the unseen branch tables into unseen
209 if (!array_key_exists($branchtable, $seen)) {
210 $unseen[] = $branchtable;
211 }
212 }
213 if (count($unseen) > 0) {
214 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
215 } else {
216 return LESSON_EOL; // has viewed all of the branch tables
217 }
5e7856af 218}
219
4b55d2af 220/**
221 * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE).
86342d63 222 *
0a4abb73 223 * @param lesson $lesson
4b55d2af 224 * @param int $pageid The id of the page that we are jumping from (?)
225 * @return int The pageid of a random page that is within a branch table
4b55d2af 226 **/
0a4abb73 227function lesson_random_question_jump($lesson, $pageid) {
646fc290 228 global $DB;
86342d63 229
ac8e16be 230 // get the lesson pages
0a4abb73 231 $params = array ("lessonid" => $lesson->id);
646fc290 232 if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
86f93345 233 print_error('cannotfindpages', 'lesson');
ac8e16be 234 }
235
236 // go up the pages till branch table
237 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
238
0a4abb73 239 if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) {
ac8e16be 240 break;
241 }
242 $pageid = $lessonpages[$pageid]->prevpageid;
243 }
244
86342d63 245 // get the pages within the branch
0a4abb73 246 $pagesinbranch = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH));
86342d63 247
ac8e16be 248 if(count($pagesinbranch) == 0) {
249 // there are no pages inside the branch, so return the next page
250 return $lessonpages[$pageid]->nextpageid;
251 } else {
252 return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id; // returns a random page id for the next page
253 }
5e7856af 254}
255
4b55d2af 256/**
257 * Calculates a user's grade for a lesson.
258 *
4b55d2af 259 * @param object $lesson The lesson that the user is taking.
4b55d2af 260 * @param int $retries The attempt number.
ff85f902 261 * @param int $userid Id of the user (optional, default current user).
88427c07 262 * @return object { nquestions => number of questions answered
263 attempts => number of question attempts
264 total => max points possible
265 earned => points earned by student
266 grade => calculated percentage grade
267 nmanual => number of manually graded questions
268 manualpoints => point value for manually graded questions }
4b55d2af 269 */
86342d63 270function lesson_grade($lesson, $ntries, $userid = 0) {
646fc290 271 global $USER, $DB;
ac8e16be 272
88427c07 273 if (empty($userid)) {
274 $userid = $USER->id;
275 }
86342d63 276
88427c07 277 // Zero out everything
278 $ncorrect = 0;
279 $nviewed = 0;
280 $score = 0;
281 $nmanual = 0;
282 $manualpoints = 0;
283 $thegrade = 0;
284 $nquestions = 0;
285 $total = 0;
286 $earned = 0;
287
646fc290 288 $params = array ("lessonid" => $lesson->id, "userid" => $userid, "retry" => $ntries);
86342d63 289 if ($useranswers = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND
646fc290 290 userid = :userid AND retry = :retry", $params, "timeseen")) {
88427c07 291 // group each try with its page
292 $attemptset = array();
293 foreach ($useranswers as $useranswer) {
86342d63 294 $attemptset[$useranswer->pageid][] = $useranswer;
ac8e16be 295 }
86342d63 296
88427c07 297 // Drop all attempts that go beyond max attempts for the lesson
298 foreach ($attemptset as $key => $set) {
299 $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts);
300 }
86342d63 301
88427c07 302 // get only the pages and their answers that the user answered
646fc290 303 list($usql, $parameters) = $DB->get_in_or_equal(array_keys($attemptset));
0a4abb73
SH
304 array_unshift($parameters, $lesson->id);
305 $pages = $DB->get_records_select("lesson_pages", "lessonid = ? AND id $usql", $parameters);
306 $answers = $DB->get_records_select("lesson_answers", "lessonid = ? AND pageid $usql", $parameters);
86342d63 307
88427c07 308 // Number of pages answered
309 $nquestions = count($pages);
310
311 foreach ($attemptset as $attempts) {
0a4abb73 312 $page = lesson_page::load($pages[end($attempts)->pageid], $lesson);
88427c07 313 if ($lesson->custom) {
314 $attempt = end($attempts);
315 // If essay question, handle it, otherwise add to score
0a4abb73
SH
316 if ($page->requires_manual_grading()) {
317 $earned += $page->earned_score($answers, $attempt);
88427c07 318 $nmanual++;
319 $manualpoints += $answers[$attempt->answerid]->score;
ab1e7c39 320 } else if (!empty($attempt->answerid)) {
0a4abb73 321 $earned += $page->earned_score($answers, $attempt);
88427c07 322 }
323 } else {
324 foreach ($attempts as $attempt) {
325 $earned += $attempt->correct;
326 }
327 $attempt = end($attempts); // doesn't matter which one
328 // If essay question, increase numbers
0a4abb73 329 if ($page->requires_manual_grading()) {
88427c07 330 $nmanual++;
331 $manualpoints++;
ac8e16be 332 }
333 }
88427c07 334 // Number of times answered
335 $nviewed += count($attempts);
336 }
86342d63 337
88427c07 338 if ($lesson->custom) {
ac8e16be 339 $bestscores = array();
88427c07 340 // Find the highest possible score per page to get our total
341 foreach ($answers as $answer) {
46341ab7 342 if(!isset($bestscores[$answer->pageid])) {
88427c07 343 $bestscores[$answer->pageid] = $answer->score;
46341ab7 344 } else if ($bestscores[$answer->pageid] < $answer->score) {
88427c07 345 $bestscores[$answer->pageid] = $answer->score;
ac8e16be 346 }
347 }
88427c07 348 $total = array_sum($bestscores);
349 } else {
350 // Check to make sure the student has answered the minimum questions
351 if ($lesson->minquestions and $nquestions < $lesson->minquestions) {
352 // Nope, increase number viewed by the amount of unanswered questions
353 $total = $nviewed + ($lesson->minquestions - $nquestions);
354 } else {
355 $total = $nviewed;
356 }
ac8e16be 357 }
88427c07 358 }
86342d63 359
88427c07 360 if ($total) { // not zero
361 $thegrade = round(100 * $earned / $total, 5);
362 }
86342d63 363
88427c07 364 // Build the grade information object
365 $gradeinfo = new stdClass;
366 $gradeinfo->nquestions = $nquestions;
367 $gradeinfo->attempts = $nviewed;
368 $gradeinfo->total = $total;
369 $gradeinfo->earned = $earned;
370 $gradeinfo->grade = $thegrade;
371 $gradeinfo->nmanual = $nmanual;
372 $gradeinfo->manualpoints = $manualpoints;
86342d63 373
88427c07 374 return $gradeinfo;
375}
376
62eda6ea 377/**
378 * Determines if a user can view the left menu. The determining factor
379 * is whether a user has a grade greater than or equal to the lesson setting
380 * of displayleftif
381 *
382 * @param object $lesson Lesson object of the current lesson
383 * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged
62eda6ea 384 **/
385function lesson_displayleftif($lesson) {
646fc290 386 global $CFG, $USER, $DB;
86342d63 387
62eda6ea 388 if (!empty($lesson->displayleftif)) {
389 // get the current user's max grade for this lesson
646fc290 390 $params = array ("userid" => $USER->id, "lessonid" => $lesson->id);
391 if ($maxgrade = $DB->get_record_sql('SELECT userid, MAX(grade) AS maxgrade FROM {lesson_grades} WHERE userid = :userid AND lessonid = :lessonid GROUP BY userid', $params)) {
62eda6ea 392 if ($maxgrade->maxgrade < $lesson->displayleftif) {
393 return 0; // turn off the displayleft
394 }
395 } else {
396 return 0; // no grades
397 }
398 }
86342d63 399
62eda6ea 400 // if we get to here, keep the original state of displayleft lesson setting
401 return $lesson->displayleft;
402}
5e7856af 403
4262a2f8 404/**
86342d63 405 *
4262a2f8 406 * @param $cm
407 * @param $lesson
408 * @param $page
409 * @return unknown_type
410 */
411function lesson_add_pretend_blocks($page, $cm, $lesson, $timer = null) {
412 $bc = lesson_menu_block_contents($cm->id, $lesson);
413 if (!empty($bc)) {
414 $regions = $page->blocks->get_regions();
415 $firstregion = reset($regions);
416 $page->blocks->add_pretend_block($bc, $firstregion);
417 }
418
419 $bc = lesson_mediafile_block_contents($cm->id, $lesson);
420 if (!empty($bc)) {
421 $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
422 }
423
424 if (!empty($timer)) {
425 $bc = lesson_clock_block_contents($cm->id, $lesson, $timer, $page);
426 if (!empty($bc)) {
427 $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
428 }
429 }
430}
431
f521f98a 432/**
86342d63 433 * If there is a media file associated with this
4262a2f8 434 * lesson, return a block_contents that displays it.
f521f98a 435 *
436 * @param int $cmid Course Module ID for this lesson
437 * @param object $lesson Full lesson record object
4262a2f8 438 * @return block_contents
f521f98a 439 **/
4262a2f8 440function lesson_mediafile_block_contents($cmid, $lesson) {
d68ccdba 441 global $OUTPUT;
0a4abb73 442 if (empty($lesson->mediafile) && empty($lesson->mediafileid)) {
4262a2f8 443 return null;
f521f98a 444 }
4262a2f8 445
0a4abb73
SH
446 $options = array();
447 $options['menubar'] = 0;
448 $options['location'] = 0;
449 $options['left'] = 5;
450 $options['top'] = 5;
451 $options['scrollbars'] = 1;
452 $options['resizable'] = 1;
453 $options['width'] = $lesson->mediawidth;
454 $options['height'] = $lesson->mediaheight;
4262a2f8 455
9bf16314
PS
456 $link = new moodle_url('/mod/lesson/mediafile.php?id='.$cmid);
457 $action = new popup_action('click', $link, 'lessonmediafile', $options);
458 $content = $OUTPUT->action_link($link, get_string('mediafilepopup', 'lesson'), $action, array('title'=>get_string('mediafilepopup', 'lesson')));
86342d63 459
4262a2f8 460 $bc = new block_contents();
461 $bc->title = get_string('linkedmedia', 'lesson');
0235e247 462 $bc->attributes['class'] = 'mediafile';
4262a2f8 463 $bc->content = $content;
464
465 return $bc;
f521f98a 466}
467
468/**
469 * If a timed lesson and not a teacher, then
4262a2f8 470 * return a block_contents containing the clock.
f521f98a 471 *
472 * @param int $cmid Course Module ID for this lesson
473 * @param object $lesson Full lesson record object
474 * @param object $timer Full timer record object
4262a2f8 475 * @return block_contents
f521f98a 476 **/
4262a2f8 477function lesson_clock_block_contents($cmid, $lesson, $timer, $page) {
478 // Display for timed lessons and for students only
f521f98a 479 $context = get_context_instance(CONTEXT_MODULE, $cmid);
4262a2f8 480 if(!$lesson->timed || has_capability('mod/lesson:manage', $context)) {
481 return null;
482 }
f521f98a 483
4262a2f8 484 $content = '<div class="jshidewhenenabled">';
0a4abb73 485 $content .= $lesson->time_remaining($timer->starttime);
4262a2f8 486 $content .= '</div>';
ba458143 487
4262a2f8 488 $clocksettings = array('starttime'=>$timer->starttime, 'servertime'=>time(),'testlength'=>($lesson->maxtime * 60));
227255b8
PS
489 $page->requires->data_for_js('clocksettings', $clocksettings);
490 $page->requires->js('/mod/lesson/timer.js');
491 $page->requires->js_function_call('show_clock');
ba458143 492
4262a2f8 493 $bc = new block_contents();
494 $bc->title = get_string('timeremaining', 'lesson');
6605ff8c 495 $bc->attributes['class'] = 'clock block';
4262a2f8 496 $bc->content = $content;
497
498 return $bc;
f521f98a 499}
500
501/**
502 * If left menu is turned on, then this will
503 * print the menu in a block
504 *
505 * @param int $cmid Course Module ID for this lesson
0a4abb73 506 * @param lesson $lesson Full lesson record object
f521f98a 507 * @return void
508 **/
4262a2f8 509function lesson_menu_block_contents($cmid, $lesson) {
646fc290 510 global $CFG, $DB;
f521f98a 511
4262a2f8 512 if (!$lesson->displayleft) {
513 return null;
514 }
f521f98a 515
0a4abb73
SH
516 $pages = $lesson->load_all_pages();
517 foreach ($pages as $page) {
518 if ((int)$page->prevpageid === 0) {
519 $pageid = $page->id;
520 break;
521 }
522 }
4262a2f8 523 $currentpageid = optional_param('pageid', $pageid, PARAM_INT);
f521f98a 524
4262a2f8 525 if (!$pageid || !$pages) {
526 return null;
f521f98a 527 }
f521f98a 528
4262a2f8 529 $content = '<a href="#maincontent" class="skip">'.get_string('skip', 'lesson')."</a>\n<div class=\"menuwrapper\">\n<ul>\n";
888f0c54 530
4262a2f8 531 while ($pageid != 0) {
532 $page = $pages[$pageid];
533
534 // Only process branch tables with display turned on
0a4abb73 535 if ($page->displayinmenublock && $page->display) {
86342d63 536 if ($page->id == $currentpageid) {
4262a2f8 537 $content .= '<li class="selected">'.format_string($page->title,true)."</li>\n";
538 } else {
539 $content .= "<li class=\"notselected\"><a href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&amp;pageid=$page->id\">".format_string($page->title,true)."</a></li>\n";
540 }
86342d63 541
888f0c54 542 }
4262a2f8 543 $pageid = $page->nextpageid;
888f0c54 544 }
4262a2f8 545 $content .= "</ul>\n</div>\n";
888f0c54 546
4262a2f8 547 $bc = new block_contents();
548 $bc->title = get_string('lessonmenu', 'lesson');
6605ff8c 549 $bc->attributes['class'] = 'menu block';
4262a2f8 550 $bc->content = $content;
888f0c54 551
4262a2f8 552 return $bc;
448052a5
SH
553}
554
555/**
556 * Adds header buttons to the page for the lesson
557 *
558 * @param object $cm
559 * @param object $context
560 * @param bool $extraeditbuttons
561 * @param int $lessonpageid
562 */
563function lesson_add_header_buttons($cm, $context, $extraeditbuttons=false, $lessonpageid=null) {
564 global $CFG, $PAGE, $OUTPUT;
92059c7e
SH
565 if (has_capability('mod/lesson:edit', $context) && $extraeditbuttons) {
566 if ($lessonpageid === null) {
567 print_error('invalidpageid', 'lesson');
568 }
569 if (!empty($lessonpageid) && $lessonpageid != LESSON_EOL) {
a6855934 570 $url = new moodle_url('/mod/lesson/lesson.php', array('id'=>$cm->id, 'redirect'=>'navigation', 'pageid'=>$lessonpageid));
5c2ed7e2 571 $PAGE->set_button($OUTPUT->single_button($url, get_string('editpagecontent', 'lesson')));
448052a5 572 }
448052a5 573 }
5c2ed7e2 574}
9b56a34f
PS
575
576/**
577 * This is a function used to detect media types and generate html code.
578 *
579 * @global object $CFG
580 * @global object $PAGE
581 * @param object $lesson
582 * @param object $context
583 * @return string $code the html code of media
584 */
585function lesson_get_media_html($lesson, $context) {
586 global $CFG, $PAGE, $OUTPUT;
587 require_once("$CFG->libdir/resourcelib.php");
588
64f93798 589 // get the media file link
d333dc20 590 $path = '/'.$context->id.'/mod_lesson/media_file/'.$lesson->id->revision.'/'.$lesson->mediafile;
64f93798 591 $url = file_encode_url($CFG->wwwroot.'/pluginfile.php', $path, false);
9b56a34f
PS
592 $title = $lesson->mediafile;
593
594 $clicktoopen = html_writer::link(new moodle_url($url), get_string('download'));
595
596 $mimetype = resourcelib_guess_url_mimetype($url);
597
598 // find the correct type and print it out
599 if (in_array($mimetype, array('image/gif','image/jpeg','image/png'))) { // It's an image
600 $code = resourcelib_embed_image($url, $title);
601
602 } else if ($mimetype == 'audio/mp3') {
603 // MP3 audio file
604 $code = resourcelib_embed_mp3($url, $title, $clicktoopen);
605
606 } else if ($mimetype == 'video/x-flv') {
607 // Flash video file
608 $code = resourcelib_embed_flashvideo($url, $title, $clicktoopen);
609
610 } else if ($mimetype == 'application/x-shockwave-flash') {
611 // Flash file
612 $code = resourcelib_embed_flash($url, $title, $clicktoopen);
613
614 } else if (substr($mimetype, 0, 10) == 'video/x-ms') {
615 // Windows Media Player file
616 $code = resourcelib_embed_mediaplayer($url, $title, $clicktoopen);
617
618 } else if ($mimetype == 'video/quicktime') {
619 // Quicktime file
620 $code = resourcelib_embed_quicktime($url, $title, $clicktoopen);
621
622 } else if ($mimetype == 'video/mpeg') {
623 // Mpeg file
624 $code = resourcelib_embed_mpeg($url, $title, $clicktoopen);
625
626 } else if ($mimetype == 'audio/x-pn-realaudio-plugin') {
627 // RealMedia file
628 $code = resourcelib_embed_real($url, $title, $clicktoopen);
629
630 } else {
631 // anything else - just try object tag enlarged as much as possible
632 $code = resourcelib_embed_general($url, $title, $clicktoopen, $mimetype);
633 }
634
635 return $code;
636}
1e7f8ea2
PS
637
638
639/**
640 * Abstract class that page type's MUST inherit from.
641 *
642 * This is the abstract class that ALL add page type forms must extend.
643 * You will notice that all but two of the methods this class contains are final.
644 * Essentially the only thing that extending classes can do is extend custom_definition.
645 * OR if it has a special requirement on creation it can extend construction_override
646 *
647 * @abstract
648 * @copyright 2009 Sam Hemelryk
649 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
650 */
651abstract class lesson_add_page_form_base extends moodleform {
652
653 /**
654 * This is the classic define that is used to identify this pagetype.
655 * Will be one of LESSON_*
656 * @var int
657 */
658 public $qtype;
659
660 /**
661 * The simple string that describes the page type e.g. truefalse, multichoice
662 * @var string
663 */
664 public $qtypestring;
665
666 /**
667 * An array of options used in the htmleditor
668 * @var array
669 */
670 protected $editoroptions = array();
671
672 /**
673 * True if this is a standard page of false if it does something special.
674 * Questions are standard pages, branch tables are not
675 * @var bool
676 */
677 protected $standard = true;
678
679 /**
680 * Each page type can and should override this to add any custom elements to
681 * the basic form that they want
682 */
683 public function custom_definition() {}
684
685 /**
686 * Used to determine if this is a standard page or a special page
687 * @return bool
688 */
689 public final function is_standard() {
690 return (bool)$this->standard;
691 }
692
693 /**
694 * Add the required basic elements to the form.
695 *
696 * This method adds the basic elements to the form including title and contents
697 * and then calls custom_definition();
698 */
699 public final function definition() {
700 $mform = $this->_form;
701 $editoroptions = $this->_customdata['editoroptions'];
702
703 $mform->addElement('header', 'qtypeheading', get_string('addaquestionpage', 'lesson', get_string($this->qtypestring, 'lesson')));
704
705 $mform->addElement('hidden', 'id');
706 $mform->setType('id', PARAM_INT);
707
708 $mform->addElement('hidden', 'pageid');
709 $mform->setType('pageid', PARAM_INT);
710
711 if ($this->standard === true) {
712 $mform->addElement('hidden', 'qtype');
713 $mform->setType('qtype', PARAM_TEXT);
714
715 $mform->addElement('text', 'title', get_string("pagetitle", "lesson"), array('size'=>70));
716 $mform->setType('title', PARAM_TEXT);
717 $this->editoroptions = array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$this->_customdata['maxbytes']);
718 $mform->addElement('editor', 'contents_editor', get_string("pagecontents", "lesson"), null, $this->editoroptions);
719 $mform->setType('contents_editor', PARAM_CLEANHTML);
720 }
721
722 $this->custom_definition();
723
724 if ($this->_customdata['edit'] === true) {
725 $mform->addElement('hidden', 'edit', 1);
726 $this->add_action_buttons(get_string('cancel'), get_string("savepage", "lesson"));
727 } else {
728 $this->add_action_buttons(get_string('cancel'), get_string("addaquestionpage", "lesson"));
729 }
730 }
731
732 /**
733 * Convenience function: Adds a jumpto select element
734 *
735 * @param string $name
736 * @param string|null $label
737 * @param int $selected The page to select by default
738 */
739 protected final function add_jumpto($name, $label=null, $selected=LESSON_NEXTPAGE) {
740 $title = get_string("jump", "lesson");
741 if ($label === null) {
742 $label = $title;
743 }
744 if (is_int($name)) {
745 $name = "jumpto[$name]";
746 }
747 $this->_form->addElement('select', $name, $label, $this->_customdata['jumpto']);
748 $this->_form->setDefault($name, $selected);
749 $this->_form->addHelpButton($name, 'jumps', 'lesson');
750 }
751
752 /**
753 * Convenience function: Adds a score input element
754 *
755 * @param string $name
756 * @param string|null $label
757 * @param mixed $value The default value
758 */
759 protected final function add_score($name, $label=null, $value=null) {
760 if ($label === null) {
761 $label = get_string("score", "lesson");
762 }
763 if (is_int($name)) {
764 $name = "score[$name]";
765 }
766 $this->_form->addElement('text', $name, $label, array('size'=>5));
767 if ($value !== null) {
768 $this->_form->setDefault($name, $value);
769 }
770 }
771
772 /**
773 * Convenience function: Adds an answer editor
774 *
775 * @param int $count The count of the element to add
776 */
777 protected final function add_answer($count) {
778 $this->_form->addElement('editor', 'answer_editor['.$count.']', get_string('answer', 'lesson'), null, array('noclean'=>true));
779 $this->_form->setDefault('answer_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
780 }
781 /**
782 * Convenience function: Adds an response editor
783 *
784 * @param int $count The count of the element to add
785 */
786 protected final function add_response($count) {
787 $this->_form->addElement('editor', 'response_editor['.$count.']', get_string('response', 'lesson'), null, array('noclean'=>true));
788 $this->_form->setDefault('response_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
789 }
790
791 /**
792 * A function that gets called upon init of this object by the calling script.
793 *
794 * This can be used to process an immediate action if required. Currently it
795 * is only used in special cases by non-standard page types.
796 *
797 * @return bool
798 */
799 public function construction_override() {
800 return true;
801 }
802}
803
804
805
806/**
807 * Class representation of a lesson
808 *
809 * This class is used the interact with, and manage a lesson once instantiated.
810 * If you need to fetch a lesson object you can do so by calling
811 *
812 * <code>
813 * lesson::load($lessonid);
814 * // or
815 * $lessonrecord = $DB->get_record('lesson', $lessonid);
816 * $lesson = new lesson($lessonrecord);
817 * </code>
818 *
819 * The class itself extends lesson_base as all classes within the lesson module should
820 *
821 * These properties are from the database
822 * @property int $id The id of this lesson
823 * @property int $course The ID of the course this lesson belongs to
824 * @property string $name The name of this lesson
825 * @property int $practice Flag to toggle this as a practice lesson
826 * @property int $modattempts Toggle to allow the user to go back and review answers
827 * @property int $usepassword Toggle the use of a password for entry
828 * @property string $password The password to require users to enter
ff85f902 829 * @property int $dependency ID of another lesson this lesson is dependent on
1e7f8ea2
PS
830 * @property string $conditions Conditions of the lesson dependency
831 * @property int $grade The maximum grade a user can achieve (%)
832 * @property int $custom Toggle custom scoring on or off
833 * @property int $ongoing Toggle display of an ongoing score
834 * @property int $usemaxgrade How retakes are handled (max=1, mean=0)
835 * @property int $maxanswers The max number of answers or branches
836 * @property int $maxattempts The maximum number of attempts a user can record
837 * @property int $review Toggle use or wrong answer review button
838 * @property int $nextpagedefault Override the default next page
839 * @property int $feedback Toggles display of default feedback
840 * @property int $minquestions Sets a minimum value of pages seen when calculating grades
841 * @property int $maxpages Maximum number of pages this lesson can contain
842 * @property int $retake Flag to allow users to retake a lesson
843 * @property int $activitylink Relate this lesson to another lesson
844 * @property string $mediafile File to pop up to or webpage to display
845 * @property int $mediaheight Sets the height of the media file popup
846 * @property int $mediawidth Sets the width of the media file popup
847 * @property int $mediaclose Toggle display of a media close button
848 * @property int $slideshow Flag for whether branch pages should be shown as slideshows
849 * @property int $width Width of slideshow
850 * @property int $height Height of slideshow
851 * @property string $bgcolor Background colour of slideshow
ff85f902 852 * @property int $displayleft Display a left menu
1e7f8ea2
PS
853 * @property int $displayleftif Sets the condition on which the left menu is displayed
854 * @property int $progressbar Flag to toggle display of a lesson progress bar
855 * @property int $highscores Flag to toggle collection of high scores
856 * @property int $maxhighscores Number of high scores to limit to
857 * @property int $available Timestamp of when this lesson becomes available
858 * @property int $deadline Timestamp of when this lesson is no longer available
859 * @property int $timemodified Timestamp when lesson was last modified
860 *
861 * These properties are calculated
862 * @property int $firstpageid Id of the first page of this lesson (prevpageid=0)
863 * @property int $lastpageid Id of the last page of this lesson (nextpageid=0)
864 *
865 * @copyright 2009 Sam Hemelryk
866 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
867 */
868class lesson extends lesson_base {
869
870 /**
871 * The id of the first page (where prevpageid = 0) gets set and retrieved by
872 * {@see get_firstpageid()} by directly calling <code>$lesson->firstpageid;</code>
873 * @var int
874 */
875 protected $firstpageid = null;
876 /**
877 * The id of the last page (where nextpageid = 0) gets set and retrieved by
878 * {@see get_lastpageid()} by directly calling <code>$lesson->lastpageid;</code>
879 * @var int
880 */
881 protected $lastpageid = null;
882 /**
883 * An array used to cache the pages associated with this lesson after the first
884 * time they have been loaded.
885 * A note to developers: If you are going to be working with MORE than one or
886 * two pages from a lesson you should probably call {@see $lesson->load_all_pages()}
887 * in order to save excess database queries.
888 * @var array An array of lesson_page objects
889 */
890 protected $pages = array();
891 /**
892 * Flag that gets set to true once all of the pages associated with the lesson
893 * have been loaded.
894 * @var bool
895 */
896 protected $loadedallpages = false;
897
898 /**
899 * Simply generates a lesson object given an array/object of properties
900 * Overrides {@see lesson_base->create()}
901 * @static
902 * @param object|array $properties
903 * @return lesson
904 */
905 public static function create($properties) {
906 return new lesson($properties);
907 }
908
909 /**
910 * Generates a lesson object from the database given its id
911 * @static
912 * @param int $lessonid
913 * @return lesson
914 */
915 public static function load($lessonid) {
916 if (!$lesson = $DB->get_record('lesson', array('id' => $lessonid))) {
917 print_error('invalidcoursemodule');
918 }
919 return new lesson($lesson);
920 }
921
922 /**
923 * Deletes this lesson from the database
924 */
925 public function delete() {
926 global $CFG, $DB;
927 require_once($CFG->libdir.'/gradelib.php');
928 require_once($CFG->dirroot.'/calendar/lib.php');
929
930 $DB->delete_records("lesson", array("id"=>$this->properties->id));;
931 $DB->delete_records("lesson_pages", array("lessonid"=>$this->properties->id));
932 $DB->delete_records("lesson_answers", array("lessonid"=>$this->properties->id));
933 $DB->delete_records("lesson_attempts", array("lessonid"=>$this->properties->id));
934 $DB->delete_records("lesson_grades", array("lessonid"=>$this->properties->id));
935 $DB->delete_records("lesson_timer", array("lessonid"=>$this->properties->id));
936 $DB->delete_records("lesson_branch", array("lessonid"=>$this->properties->id));
937 $DB->delete_records("lesson_high_scores", array("lessonid"=>$this->properties->id));
938 if ($events = $DB->get_records('event', array("modulename"=>'lesson', "instance"=>$this->properties->id))) {
939 foreach($events as $event) {
940 $event = calendar_event::load($event);
941 $event->delete();
942 }
943 }
944
945 grade_update('mod/lesson', $this->properties->course, 'mod', 'lesson', $this->properties->id, 0, NULL, array('deleted'=>1));
946 return true;
947 }
948
949 /**
950 * Fetches messages from the session that may have been set in previous page
951 * actions.
952 *
953 * <code>
954 * // Do not call this method directly instead use
955 * $lesson->messages;
956 * </code>
957 *
958 * @return array
959 */
960 protected function get_messages() {
961 global $SESSION;
962
963 $messages = array();
964 if (!empty($SESSION->lesson_messages) && is_array($SESSION->lesson_messages) && array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
965 $messages = $SESSION->lesson_messages[$this->properties->id];
966 unset($SESSION->lesson_messages[$this->properties->id]);
967 }
968
969 return $messages;
970 }
971
972 /**
973 * Get all of the attempts for the current user.
974 *
975 * @param int $retries
976 * @param bool $correct Optional: only fetch correct attempts
977 * @param int $pageid Optional: only fetch attempts at the given page
978 * @param int $userid Optional: defaults to the current user if not set
979 * @return array|false
980 */
981 public function get_attempts($retries, $correct=false, $pageid=null, $userid=null) {
982 global $USER, $DB;
983 $params = array("lessonid"=>$this->properties->id, "userid"=>$userid, "retry"=>$retries);
984 if ($correct) {
985 $params['correct'] = 1;
986 }
987 if ($pageid !== null) {
988 $params['pageid'] = $pageid;
989 }
990 if ($userid === null) {
991 $params['userid'] = $USER->id;
992 }
993 return $DB->get_records('lesson_attempts', $params, 'timeseen DESC');
994 }
995
996 /**
997 * Returns the first page for the lesson or false if there isn't one.
998 *
999 * This method should be called via the magic method __get();
1000 * <code>
1001 * $firstpage = $lesson->firstpage;
1002 * </code>
1003 *
1004 * @return lesson_page|bool Returns the lesson_page specialised object or false
1005 */
1006 protected function get_firstpage() {
1007 $pages = $this->load_all_pages();
1008 if (count($pages) > 0) {
1009 foreach ($pages as $page) {
1010 if ((int)$page->prevpageid === 0) {
1011 return $page;
1012 }
1013 }
1014 }
1015 return false;
1016 }
1017
1018 /**
1019 * Returns the last page for the lesson or false if there isn't one.
1020 *
1021 * This method should be called via the magic method __get();
1022 * <code>
1023 * $lastpage = $lesson->lastpage;
1024 * </code>
1025 *
1026 * @return lesson_page|bool Returns the lesson_page specialised object or false
1027 */
1028 protected function get_lastpage() {
1029 $pages = $this->load_all_pages();
1030 if (count($pages) > 0) {
1031 foreach ($pages as $page) {
1032 if ((int)$page->nextpageid === 0) {
1033 return $page;
1034 }
1035 }
1036 }
1037 return false;
1038 }
1039
1040 /**
1041 * Returns the id of the first page of this lesson. (prevpageid = 0)
1042 * @return int
1043 */
1044 protected function get_firstpageid() {
1045 global $DB;
1046 if ($this->firstpageid == null) {
1047 if (!$this->loadedallpages) {
1048 $firstpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'prevpageid'=>0));
1049 if (!$firstpageid) {
1050 print_error('cannotfindfirstpage', 'lesson');
1051 }
1052 $this->firstpageid = $firstpageid;
1053 } else {
1054 $firstpage = $this->get_firstpage();
1055 $this->firstpageid = $firstpage->id;
1056 }
1057 }
1058 return $this->firstpageid;
1059 }
1060
1061 /**
1062 * Returns the id of the last page of this lesson. (nextpageid = 0)
1063 * @return int
1064 */
1065 public function get_lastpageid() {
1066 global $DB;
1067 if ($this->lastpageid == null) {
1068 if (!$this->loadedallpages) {
1069 $lastpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'nextpageid'=>0));
1070 if (!$lastpageid) {
1071 print_error('cannotfindlastpage', 'lesson');
1072 }
1073 $this->lastpageid = $lastpageid;
1074 } else {
1075 $lastpageid = $this->get_lastpage();
1076 $this->lastpageid = $lastpageid->id;
1077 }
1078 }
1079
1080 return $this->lastpageid;
1081 }
1082
1083 /**
1084 * Gets the next page to display after the one that is provided.
1085 * @param int $nextpageid
1086 * @return bool
1087 */
1088 public function get_next_page($nextpageid) {
1089 global $USER;
1090 $allpages = $this->load_all_pages();
1091 if ($this->properties->nextpagedefault) {
1092 // in Flash Card mode...first get number of retakes
1093 shuffle($allpages);
1094 $found = false;
1095 if ($this->properties->nextpagedefault == LESSON_UNSEENPAGE) {
1096 foreach ($allpages as $nextpage) {
1097 if (!$DB->count_records("lesson_attempts", array("pageid"=>$nextpage->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
1098 $found = true;
1099 break;
1100 }
1101 }
1102 } elseif ($this->properties->nextpagedefault == LESSON_UNANSWEREDPAGE) {
1103 foreach ($allpages as $nextpage) {
1104 if (!$DB->count_records("lesson_attempts", array('pageid'=>$nextpage->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
1105 $found = true;
1106 break;
1107 }
1108 }
1109 }
1110 if ($found) {
1111 if ($this->properties->maxpages) {
1112 // check number of pages viewed (in the lesson)
1113 $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$USER->id));
1114 if ($DB->count_records("lesson_attempts", array("lessonid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes)) >= $this->properties->maxpages) {
1115 return false;
1116 }
1117 }
1118 return $nextpage;
1119 }
1120 }
1121 // In a normal lesson mode
1122 foreach ($allpages as $nextpage) {
1123 if ((int)$nextpage->id===(int)$nextpageid) {
1124 return $nextpage;
1125 }
1126 }
1127 return false;
1128 }
1129
1130 /**
1131 * Sets a message against the session for this lesson that will displayed next
1132 * time the lesson processes messages
1133 *
1134 * @param string $message
1135 * @param string $class
1136 * @param string $align
1137 * @return bool
1138 */
1139 public function add_message($message, $class="notifyproblem", $align='center') {
1140 global $SESSION;
1141
1142 if (empty($SESSION->lesson_messages) || !is_array($SESSION->lesson_messages)) {
1143 $SESSION->lesson_messages = array();
1144 $SESSION->lesson_messages[$this->properties->id] = array();
1145 } else if (!array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
1146 $SESSION->lesson_messages[$this->properties->id] = array();
1147 }
1148
1149 $SESSION->lesson_messages[$this->properties->id][] = array($message, $class, $align);
1150
1151 return true;
1152 }
1153
1154 /**
1155 * Check if the lesson is accessible at the present time
1156 * @return bool True if the lesson is accessible, false otherwise
1157 */
1158 public function is_accessible() {
1159 $available = $this->properties->available;
1160 $deadline = $this->properties->deadline;
1161 return (($available == 0 || time() >= $available) && ($deadline == 0 || time() < $deadline));
1162 }
1163
1164 /**
1165 * Starts the lesson time for the current user
1166 * @return bool Returns true
1167 */
1168 public function start_timer() {
1169 global $USER, $DB;
1170 $USER->startlesson[$this->properties->id] = true;
1171 $startlesson = new stdClass;
1172 $startlesson->lessonid = $this->properties->id;
1173 $startlesson->userid = $USER->id;
1174 $startlesson->starttime = time();
1175 $startlesson->lessontime = time();
1176 $DB->insert_record('lesson_timer', $startlesson);
1177 if ($this->properties->timed) {
1178 $this->add_message(get_string('maxtimewarning', 'lesson', $this->properties->maxtime), 'center');
1179 }
1180 return true;
1181 }
1182
1183 /**
1184 * Updates the timer to the current time and returns the new timer object
1185 * @param bool $restart If set to true the timer is restarted
1186 * @param bool $continue If set to true AND $restart=true then the timer
1187 * will continue from a previous attempt
1188 * @return stdClass The new timer
1189 */
1190 public function update_timer($restart=false, $continue=false) {
1191 global $USER, $DB;
1192 // clock code
1193 // get time information for this user
1194 if (!$timer = $DB->get_records('lesson_timer', array ("lessonid" => $this->properties->id, "userid" => $USER->id), 'starttime DESC', '*', 0, 1)) {
1195 print_error('cannotfindtimer', 'lesson');
1196 } else {
1197 $timer = current($timer); // this will get the latest start time record
1198 }
1199
1200 if ($restart) {
1201 if ($continue) {
1202 // continue a previous test, need to update the clock (think this option is disabled atm)
1203 $timer->starttime = time() - ($timer->lessontime - $timer->starttime);
1204 } else {
1205 // starting over, so reset the clock
1206 $timer->starttime = time();
1207 }
1208 }
1209
1210 $timer->lessontime = time();
1211 $DB->update_record('lesson_timer', $timer);
1212 return $timer;
1213 }
1214
1215 /**
1216 * Updates the timer to the current time then stops it by unsetting the user var
1217 * @return bool Returns true
1218 */
1219 public function stop_timer() {
1220 global $USER, $DB;
1221 unset($USER->startlesson[$this->properties->id]);
1222 return $this->update_timer(false, false);
1223 }
1224
1225 /**
1226 * Checks to see if the lesson has pages
1227 */
1228 public function has_pages() {
1229 global $DB;
1230 $pagecount = $DB->count_records('lesson_pages', array('lessonid'=>$this->properties->id));
1231 return ($pagecount>0);
1232 }
1233
1234 /**
1235 * Returns the link for the related activity
1236 * @return array|false
1237 */
1238 public function link_for_activitylink() {
1239 global $DB;
1240 $module = $DB->get_record('course_modules', array('id' => $this->properties->activitylink));
1241 if ($module) {
1242 $modname = $DB->get_field('modules', 'name', array('id' => $module->module));
1243 if ($modname) {
1244 $instancename = $DB->get_field($modname, 'name', array('id' => $module->instance));
1245 if ($instancename) {
1246 return html_writer::link(new moodle_url('/mod/'.$modname.'/view.php', array('id'=>$this->properties->activitylink)),
1247 get_string('returnto', 'lesson', get_string('activitylinkname', 'lesson', $instancename)),
1248 array('class'=>'centerpadded lessonbutton standardbutton'));
1249 }
1250 }
1251 }
1252 return '';
1253 }
1254
1255 /**
1256 * Loads the requested page.
1257 *
1258 * This function will return the requested page id as either a specialised
1259 * lesson_page object OR as a generic lesson_page.
1260 * If the page has been loaded previously it will be returned from the pages
1261 * array, otherwise it will be loaded from the database first
1262 *
1263 * @param int $pageid
1264 * @return lesson_page A lesson_page object or an object that extends it
1265 */
1266 public function load_page($pageid) {
1267 if (!array_key_exists($pageid, $this->pages)) {
1268 $manager = lesson_page_type_manager::get($this);
1269 $this->pages[$pageid] = $manager->load_page($pageid, $this);
1270 }
1271 return $this->pages[$pageid];
1272 }
1273
1274 /**
1275 * Loads ALL of the pages for this lesson
1276 *
1277 * @return array An array containing all pages from this lesson
1278 */
1279 public function load_all_pages() {
1280 if (!$this->loadedallpages) {
1281 $manager = lesson_page_type_manager::get($this);
1282 $this->pages = $manager->load_all_pages($this);
1283 $this->loadedallpages = true;
1284 }
1285 return $this->pages;
1286 }
1287
1288 /**
ff85f902 1289 * Determines if a jumpto value is correct or not.
1e7f8ea2
PS
1290 *
1291 * returns true if jumpto page is (logically) after the pageid page or
1292 * if the jumpto value is a special value. Returns false in all other cases.
1293 *
1294 * @param int $pageid Id of the page from which you are jumping from.
1295 * @param int $jumpto The jumpto number.
1296 * @return boolean True or false after a series of tests.
1297 **/
1298 public function jumpto_is_correct($pageid, $jumpto) {
1299 global $DB;
1300
1301 // first test the special values
1302 if (!$jumpto) {
1303 // same page
1304 return false;
1305 } elseif ($jumpto == LESSON_NEXTPAGE) {
1306 return true;
1307 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
1308 return true;
1309 } elseif ($jumpto == LESSON_RANDOMPAGE) {
1310 return true;
1311 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
1312 return true;
1313 } elseif ($jumpto == LESSON_EOL) {
1314 return true;
1315 }
1316
1317 $pages = $this->load_all_pages();
1318 $apageid = $pages[$pageid]->nextpageid;
1319 while ($apageid != 0) {
1320 if ($jumpto == $apageid) {
1321 return true;
1322 }
1323 $apageid = $pages[$apageid]->nextpageid;
1324 }
1325 return false;
1326 }
1327
1328 /**
1329 * Returns the time a user has remaining on this lesson
1330 * @param int $starttime Starttime timestamp
1331 * @return string
1332 */
1333 public function time_remaining($starttime) {
1334 $timeleft = $starttime + $this->maxtime * 60 - time();
1335 $hours = floor($timeleft/3600);
1336 $timeleft = $timeleft - ($hours * 3600);
1337 $minutes = floor($timeleft/60);
1338 $secs = $timeleft - ($minutes * 60);
1339
1340 if ($minutes < 10) {
1341 $minutes = "0$minutes";
1342 }
1343 if ($secs < 10) {
1344 $secs = "0$secs";
1345 }
1346 $output = array();
1347 $output[] = $hours;
1348 $output[] = $minutes;
1349 $output[] = $secs;
1350 $output = implode(':', $output);
1351 return $output;
1352 }
1353
1354 /**
1355 * Interprets LESSON_CLUSTERJUMP jumpto value.
1356 *
1357 * This will select a page randomly
ff85f902 1358 * and the page selected will be inbetween a cluster page and end of clutter or end of lesson
1e7f8ea2
PS
1359 * and the page selected will be a page that has not been viewed already
1360 * and if any pages are within a branch table or end of branch then only 1 page within
1361 * the branch table or end of branch will be randomly selected (sub clustering).
1362 *
1363 * @param int $pageid Id of the current page from which we are jumping from.
1364 * @param int $userid Id of the user.
1365 * @return int The id of the next page.
1366 **/
1367 public function cluster_jump($pageid, $userid=null) {
1368 global $DB, $USER;
1369
1370 if ($userid===null) {
1371 $userid = $USER->id;
1372 }
1373 // get the number of retakes
1374 if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$userid))) {
1375 $retakes = 0;
1376 }
1377 // get all the lesson_attempts aka what the user has seen
1378 $seenpages = array();
1379 if ($attempts = $this->get_attempts($retakes)) {
1380 foreach ($attempts as $attempt) {
1381 $seenpages[$attempt->pageid] = $attempt->pageid;
1382 }
1383
1384 }
1385
1386 // get the lesson pages
1387 $lessonpages = $this->load_all_pages();
1388 // find the start of the cluster
1389 while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
1390 if ($lessonpages[$pageid]->qtype == LESSON_PAGE_CLUSTER) {
1391 break;
1392 }
1393 $pageid = $lessonpages[$pageid]->prevpageid;
1394 }
1395
1396 $clusterpages = array();
1397 $clusterpages = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_ENDOFCLUSTER));
1398 $unseen = array();
1399 foreach ($clusterpages as $key=>$cluster) {
1400 if ($cluster->type !== lesson_page::TYPE_QUESTION) {
1401 unset($clusterpages[$key]);
1402 } elseif ($cluster->is_unseen($seenpages)) {
1403 $unseen[] = $cluster;
1404 }
1405 }
1406
1407 if (count($unseen) > 0) {
1408 // it does not contain elements, then use exitjump, otherwise find out next page/branch
1409 $nextpage = $unseen[rand(0, count($unseen)-1)];
1410 if ($nextpage->qtype == LESSON_PAGE_BRANCHTABLE) {
1411 // if branch table, then pick a random page inside of it
1412 $branchpages = $this->get_sub_pages_of($nextpage->id, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH));
1413 return $branchpages[rand(0, count($branchpages)-1)]->id;
1414 } else { // otherwise, return the page's id
1415 return $nextpage->id;
1416 }
1417 } else {
1418 // seen all there is to see, leave the cluster
1419 if (end($clusterpages)->nextpageid == 0) {
1420 return LESSON_EOL;
1421 } else {
1422 $clusterendid = $pageid;
1423 while ($clusterendid != 0) { // this condition should not be satisfied... should be a cluster page
1424 if ($lessonpages[$clusterendid]->qtype == LESSON_PAGE_CLUSTER) {
1425 break;
1426 }
1427 $clusterendid = $lessonpages[$clusterendid]->prevpageid;
1428 }
1429 $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $clusterendid, "lessonid" => $this->properties->id));
1430 if ($exitjump == LESSON_NEXTPAGE) {
1431 $exitjump = $lessonpages[$pageid]->nextpageid;
1432 }
1433 if ($exitjump == 0) {
1434 return LESSON_EOL;
1435 } else if (in_array($exitjump, array(LESSON_EOL, LESSON_PREVIOUSPAGE))) {
1436 return $exitjump;
1437 } else {
1438 if (!array_key_exists($exitjump, $lessonpages)) {
1439 $found = false;
1440 foreach ($lessonpages as $page) {
1441 if ($page->id === $clusterendid) {
1442 $found = true;
1443 } else if ($page->qtype == LESSON_PAGE_ENDOFCLUSTER) {
1444 $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $page->id, "lessonid" => $this->properties->id));
1445 break;
1446 }
1447 }
1448 }
1449 if (!array_key_exists($exitjump, $lessonpages)) {
1450 return LESSON_EOL;
1451 }
1452 return $exitjump;
1453 }
1454 }
1455 }
1456 }
1457
1458 /**
1459 * Finds all pages that appear to be a subtype of the provided pageid until
1460 * an end point specified within $ends is encountered or no more pages exist
1461 *
1462 * @param int $pageid
1463 * @param array $ends An array of LESSON_PAGE_* types that signify an end of
1464 * the subtype
1465 * @return array An array of specialised lesson_page objects
1466 */
1467 public function get_sub_pages_of($pageid, array $ends) {
1468 $lessonpages = $this->load_all_pages();
1469 $pageid = $lessonpages[$pageid]->nextpageid; // move to the first page after the branch table
1470 $pages = array();
1471
1472 while (true) {
1473 if ($pageid == 0 || in_array($lessonpages[$pageid]->qtype, $ends)) {
1474 break;
1475 }
1476 $pages[] = $lessonpages[$pageid];
1477 $pageid = $lessonpages[$pageid]->nextpageid;
1478 }
1479
1480 return $pages;
1481 }
1482
1483 /**
1484 * Checks to see if the specified page[id] is a subpage of a type specified in
1485 * the $types array, until either there are no more pages of we find a type
ff85f902 1486 * corresponding to that of a type specified in $ends
1e7f8ea2
PS
1487 *
1488 * @param int $pageid The id of the page to check
1489 * @param array $types An array of types that would signify this page was a subpage
1490 * @param array $ends An array of types that mean this is not a subpage
1491 * @return bool
1492 */
1493 public function is_sub_page_of_type($pageid, array $types, array $ends) {
1494 $pages = $this->load_all_pages();
1495 $pageid = $pages[$pageid]->prevpageid; // move up one
1496
1497 array_unshift($ends, 0);
1498 // go up the pages till branch table
1499 while (true) {
1500 if ($pageid==0 || in_array($pages[$pageid]->qtype, $ends)) {
1501 return false;
1502 } else if (in_array($pages[$pageid]->qtype, $types)) {
1503 return true;
1504 }
1505 $pageid = $pages[$pageid]->prevpageid;
1506 }
1507 }
1508}
1509
1510
1511/**
1512 * Abstract class to provide a core functions to the all lesson classes
1513 *
1514 * This class should be abstracted by ALL classes with the lesson module to ensure
1515 * that all classes within this module can be interacted with in the same way.
1516 *
1517 * This class provides the user with a basic properties array that can be fetched
ff85f902 1518 * or set via magic methods, or alternatively by defining methods get_blah() or
1e7f8ea2
PS
1519 * set_blah() within the extending object.
1520 *
1521 * @copyright 2009 Sam Hemelryk
1522 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1523 */
1524abstract class lesson_base {
1525
1526 /**
1527 * An object containing properties
1528 * @var stdClass
1529 */
1530 protected $properties;
1531
1532 /**
1533 * The constructor
1534 * @param stdClass $properties
1535 */
1536 public function __construct($properties) {
1537 $this->properties = (object)$properties;
1538 }
1539
1540 /**
1541 * Magic property method
1542 *
1543 * Attempts to call a set_$key method if one exists otherwise falls back
1544 * to simply set the property
1545 *
1546 * @param string $key
1547 * @param mixed $value
1548 */
1549 public function __set($key, $value) {
1550 if (method_exists($this, 'set_'.$key)) {
1551 $this->{'set_'.$key}($value);
1552 }
1553 $this->properties->{$key} = $value;
1554 }
1555
1556 /**
1557 * Magic get method
1558 *
1559 * Attempts to call a get_$key method to return the property and ralls over
1560 * to return the raw property
1561 *
1562 * @param str $key
1563 * @return mixed
1564 */
1565 public function __get($key) {
1566 if (method_exists($this, 'get_'.$key)) {
1567 return $this->{'get_'.$key}();
1568 }
1569 return $this->properties->{$key};
1570 }
1571
1572 /**
1573 * Stupid PHP needs an isset magic method if you use the get magic method and
1574 * still want empty calls to work.... blah ~!
1575 *
1576 * @param string $key
1577 * @return bool
1578 */
1579 public function __isset($key) {
1580 if (method_exists($this, 'get_'.$key)) {
1581 $val = $this->{'get_'.$key}();
1582 return !empty($val);
1583 }
1584 return !empty($this->properties->{$key});
1585 }
1586
1587 /**
ff85f902 1588 * If overridden should create a new instance, save it in the DB and return it
1e7f8ea2
PS
1589 */
1590 public static function create() {}
1591 /**
ff85f902 1592 * If overridden should load an instance from the DB and return it
1e7f8ea2
PS
1593 */
1594 public static function load() {}
1595 /**
1596 * Fetches all of the properties of the object
1597 * @return stdClass
1598 */
1599 public function properties() {
1600 return $this->properties;
1601 }
1602}
1603
1604
1605/**
1606 * Abstract class representation of a page associated with a lesson.
1607 *
1608 * This class should MUST be extended by all specialised page types defined in
1609 * mod/lesson/pagetypes/.
1610 * There are a handful of abstract methods that need to be defined as well as
1611 * severl methods that can optionally be defined in order to make the page type
1612 * operate in the desired way
1613 *
1614 * Database properties
1615 * @property int $id The id of this lesson page
1616 * @property int $lessonid The id of the lesson this page belongs to
1617 * @property int $prevpageid The id of the page before this one
1618 * @property int $nextpageid The id of the next page in the page sequence
1619 * @property int $qtype Identifies the page type of this page
1620 * @property int $qoption Used to record page type specific options
1621 * @property int $layout Used to record page specific layout selections
1622 * @property int $display Used to record page specific display selections
1623 * @property int $timecreated Timestamp for when the page was created
1624 * @property int $timemodified Timestamp for when the page was last modified
1625 * @property string $title The title of this page
1626 * @property string $contents The rich content shown to describe the page
1627 * @property int $contentsformat The format of the contents field
1628 *
1629 * Calculated properties
1630 * @property-read array $answers An array of answers for this page
1631 * @property-read bool $displayinmenublock Toggles display in the left menu block
1632 * @property-read array $jumps An array containing all the jumps this page uses
1633 * @property-read lesson $lesson The lesson this page belongs to
1634 * @property-read int $type The type of the page [question | structure]
1635 * @property-read typeid The unique identifier for the page type
1636 * @property-read typestring The string that describes this page type
1637 *
1638 * @abstract
1639 * @copyright 2009 Sam Hemelryk
1640 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1641 */
1642abstract class lesson_page extends lesson_base {
1643
1644 /**
1645 * A reference to the lesson this page belongs to
1646 * @var lesson
1647 */
1648 protected $lesson = null;
1649 /**
1650 * Contains the answers to this lesson_page once loaded
1651 * @var null|array
1652 */
1653 protected $answers = null;
1654 /**
1655 * This sets the type of the page, can be one of the constants defined below
1656 * @var int
1657 */
1658 protected $type = 0;
1659
1660 /**
1661 * Constants used to identify the type of the page
1662 */
1663 const TYPE_QUESTION = 0;
1664 const TYPE_STRUCTURE = 1;
1665
1666 /**
1667 * This method should return the integer used to identify the page type within
ff85f902 1668 * the database and throughout code. This maps back to the defines used in 1.x
1e7f8ea2
PS
1669 * @abstract
1670 * @return int
1671 */
1672 abstract protected function get_typeid();
1673 /**
1674 * This method should return the string that describes the pagetype
1675 * @abstract
1676 * @return string
1677 */
1678 abstract protected function get_typestring();
1679
1680 /**
1681 * This method gets called to display the page to the user taking the lesson
1682 * @abstract
1683 * @param object $renderer
1684 * @param object $attempt
1685 * @return string
1686 */
1687 abstract public function display($renderer, $attempt);
1688
1689 /**
1690 * Creates a new lesson_page within the database and returns the correct pagetype
1691 * object to use to interact with the new lesson
1692 *
1693 * @final
1694 * @static
1695 * @param object $properties
1696 * @param lesson $lesson
1697 * @return lesson_page Specialised object that extends lesson_page
1698 */
1699 final public static function create($properties, lesson $lesson, $context, $maxbytes) {
1700 global $DB;
1701 $newpage = new stdClass;
1702 $newpage->title = $properties->title;
1703 $newpage->contents = $properties->contents_editor['text'];
1704 $newpage->contentsformat = $properties->contents_editor['format'];
1705 $newpage->lessonid = $lesson->id;
1706 $newpage->timecreated = time();
1707 $newpage->qtype = $properties->qtype;
1708 $newpage->qoption = (isset($properties->qoption))?1:0;
1709 $newpage->layout = (isset($properties->layout))?1:0;
1710 $newpage->display = (isset($properties->display))?1:0;
1711 $newpage->prevpageid = 0; // this is a first page
1712 $newpage->nextpageid = 0; // this is the only page
1713
1714 if ($properties->pageid) {
1715 $prevpage = $DB->get_record("lesson_pages", array("id" => $properties->pageid), 'id, nextpageid');
1716 if (!$prevpage) {
1717 print_error('cannotfindpages', 'lesson');
1718 }
1719 $newpage->prevpageid = $prevpage->id;
1720 $newpage->nextpageid = $prevpage->nextpageid;
1721 } else {
1722 $nextpage = $DB->get_record('lesson_pages', array('lessonid'=>$lesson->id, 'prevpageid'=>0), 'id');
1723 if ($nextpage) {
1724 // This is the first page, there are existing pages put this at the start
1725 $newpage->nextpageid = $nextpage->id;
1726 }
1727 }
1728
1729 $newpage->id = $DB->insert_record("lesson_pages", $newpage);
1730
1731 $editor = new stdClass;
1732 $editor->id = $newpage->id;
1733 $editor->contents_editor = $properties->contents_editor;
1734 $editor = file_postupdate_standard_editor($editor, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $editor->id);
1735 $DB->update_record("lesson_pages", $editor);
1736
1737 if ($newpage->prevpageid > 0) {
1738 $DB->set_field("lesson_pages", "nextpageid", $newpage->id, array("id" => $newpage->prevpageid));
1739 }
1740 if ($newpage->nextpageid > 0) {
1741 $DB->set_field("lesson_pages", "prevpageid", $newpage->id, array("id" => $newpage->nextpageid));
1742 }
1743
1744 $page = lesson_page::load($newpage, $lesson);
1745 $page->create_answers($properties);
1746
1747 $lesson->add_message(get_string('insertedpage', 'lesson').': '.format_string($newpage->title, true), 'notifysuccess');
1748
1749 return $page;
1750 }
1751
1752 /**
1753 * This method loads a page object from the database and returns it as a
1754 * specialised object that extends lesson_page
1755 *
1756 * @final
1757 * @static
1758 * @param int $id
1759 * @param lesson $lesson
1760 * @return lesson_page Specialised lesson_page object
1761 */
1762 final public static function load($id, lesson $lesson) {
1763 global $DB;
1764
1765 if (is_object($id) && !empty($id->qtype)) {
1766 $page = $id;
1767 } else {
1768 $page = $DB->get_record("lesson_pages", array("id" => $id));
1769 if (!$page) {
1770 print_error('cannotfindpages', 'lesson');
1771 }
1772 }
1773 $manager = lesson_page_type_manager::get($lesson);
1774
1775 $class = 'lesson_page_type_'.$manager->get_page_type_idstring($page->qtype);
1776 if (!class_exists($class)) {
1777 $class = 'lesson_page';
1778 }
1779
1780 return new $class($page, $lesson);
1781 }
1782
1783 /**
1784 * Deletes a lesson_page from the database as well as any associated records.
1785 * @final
1786 * @return bool
1787 */
1788 final public function delete() {
1789 global $DB;
1790 // first delete all the associated records...
1791 $DB->delete_records("lesson_attempts", array("pageid" => $this->properties->id));
1792 // ...now delete the answers...
1793 $DB->delete_records("lesson_answers", array("pageid" => $this->properties->id));
1794 // ..and the page itself
1795 $DB->delete_records("lesson_pages", array("id" => $this->properties->id));
1796
1797 // repair the hole in the linkage
1798 if (!$this->properties->prevpageid && !$this->properties->nextpageid) {
1799 //This is the only page, no repair needed
1800 } elseif (!$this->properties->prevpageid) {
1801 // this is the first page...
1802 $page = $this->lesson->load_page($this->properties->nextpageid);
1803 $page->move(null, 0);
1804 } elseif (!$this->properties->nextpageid) {
1805 // this is the last page...
1806 $page = $this->lesson->load_page($this->properties->prevpageid);
1807 $page->move(0);
1808 } else {
1809 // page is in the middle...
1810 $prevpage = $this->lesson->load_page($this->properties->prevpageid);
1811 $nextpage = $this->lesson->load_page($this->properties->nextpageid);
1812
1813 $prevpage->move($nextpage->id);
1814 $nextpage->move(null, $prevpage->id);
1815 }
1816 return true;
1817 }
1818
1819 /**
1820 * Moves a page by updating its nextpageid and prevpageid values within
1821 * the database
1822 *
1823 * @final
1824 * @param int $nextpageid
1825 * @param int $prevpageid
1826 */
1827 final public function move($nextpageid=null, $prevpageid=null) {
1828 global $DB;
1829 if ($nextpageid === null) {
1830 $nextpageid = $this->properties->nextpageid;
1831 }
1832 if ($prevpageid === null) {
1833 $prevpageid = $this->properties->prevpageid;
1834 }
1835 $obj = new stdClass;
1836 $obj->id = $this->properties->id;
1837 $obj->prevpageid = $prevpageid;
1838 $obj->nextpageid = $nextpageid;
1839 $DB->update_record('lesson_pages', $obj);
1840 }
1841
1842 /**
1843 * Returns the answers that are associated with this page in the database
1844 *
1845 * @final
1846 * @return array
1847 */
1848 final public function get_answers() {
1849 global $DB;
1850 if ($this->answers === null) {
1851 $this->answers = array();
1852 $answers = $DB->get_records('lesson_answers', array('pageid'=>$this->properties->id, 'lessonid'=>$this->lesson->id), 'id');
1853 if (!$answers) {
1854 debugging(get_string('cannotfindanswer', 'lesson'));
1855 return array();
1856 }
1857 foreach ($answers as $answer) {
1858 $this->answers[count($this->answers)] = new lesson_page_answer($answer);
1859 }
1860 }
1861 return $this->answers;
1862 }
1863
1864 /**
1865 * Returns the lesson this page is associated with
1866 * @final
1867 * @return lesson
1868 */
1869 final protected function get_lesson() {
1870 return $this->lesson;
1871 }
1872
1873 /**
1874 * Returns the type of page this is. Not to be confused with page type
1875 * @final
1876 * @return int
1877 */
1878 final protected function get_type() {
1879 return $this->type;
1880 }
1881
1882 /**
1883 * Records an attempt at this page
1884 *
1885 * @final
1886 * @param stdClass $context
1887 * @return stdClass Returns the result of the attempt
1888 */
1889 final public function record_attempt($context) {
1890 global $DB, $USER, $OUTPUT;
1891
1892 /**
ff85f902 1893 * This should be overridden by each page type to actually check the response
1e7f8ea2
PS
1894 * against what ever custom criteria they have defined
1895 */
1896 $result = $this->check_answer();
1897
1898 $result->attemptsremaining = 0;
1899 $result->maxattemptsreached = false;
1900
1901 if ($result->noanswer) {
1902 $result->newpageid = $this->properties->id; // display same page again
1903 $result->feedback = get_string('noanswer', 'lesson');
1904 } else {
1905 if (!has_capability('mod/lesson:manage', $context)) {
1906 $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
1907 // record student's attempt
1908 $attempt = new stdClass;
1909 $attempt->lessonid = $this->lesson->id;
1910 $attempt->pageid = $this->properties->id;
1911 $attempt->userid = $USER->id;
1912 $attempt->answerid = $result->answerid;
1913 $attempt->retry = $nretakes;
1914 $attempt->correct = $result->correctanswer;
1915 if($result->userresponse !== null) {
1916 $attempt->useranswer = $result->userresponse;
1917 }
1918
1919 $attempt->timeseen = time();
1920 // if allow modattempts, then update the old attempt record, otherwise, insert new answer record
1921 if (isset($USER->modattempts[$this->lesson->id])) {
1922 $attempt->retry = $nretakes - 1; // they are going through on review, $nretakes will be too high
1923 }
1924
1925 $DB->insert_record("lesson_attempts", $attempt);
1926 // "number of attempts remaining" message if $this->lesson->maxattempts > 1
1927 // displaying of message(s) is at the end of page for more ergonomic display
1928 if (!$result->correctanswer && ($result->newpageid == 0)) {
1929 // wrong answer and student is stuck on this page - check how many attempts
1930 // the student has had at this page/question
1931 $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id),"retry", $nretakes);
1932 // retreive the number of attempts left counter for displaying at bottom of feedback page
1933 if ($nattempts >= $this->lesson->maxattempts) {
1934 if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
1935 $result->maxattemptsreached = true;
1936 }
1937 $result->newpageid = LESSON_NEXTPAGE;
1938 } else if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
1939 $result->attemptsremaining = $this->lesson->maxattempts - $nattempts;
1940 }
1941 }
1942 }
1943 // TODO: merge this code with the jump code below. Convert jumpto page into a proper page id
1944 if ($result->newpageid == 0) {
1945 $result->newpageid = $this->properties->id;
1946 } elseif ($result->newpageid == LESSON_NEXTPAGE) {
1947 $nextpage = $this->lesson->get_next_page($this->properties->nextpageid);
1948 if ($nextpage === false) {
1949 $result->newpageid = LESSON_EOL;
1950 } else {
1951 $result->newpageid = $nextpage->id;
1952 }
1953 }
1954
1955 // Determine default feedback if necessary
1956 if (empty($result->response)) {
1957 if (!$this->lesson->feedback && !$result->noanswer && !($this->lesson->review & !$result->correctanswer && !$result->isessayquestion)) {
1958 // These conditions have been met:
1959 // 1. The lesson manager has not supplied feedback to the student
1960 // 2. Not displaying default feedback
1961 // 3. The user did provide an answer
1962 // 4. We are not reviewing with an incorrect answer (and not reviewing an essay question)
1963
1964 $result->nodefaultresponse = true; // This will cause a redirect below
1965 } else if ($result->isessayquestion) {
1966 $result->response = get_string('defaultessayresponse', 'lesson');
1967 } else if ($result->correctanswer) {
1968 $result->response = get_string('thatsthecorrectanswer', 'lesson');
1969 } else {
1970 $result->response = get_string('thatsthewronganswer', 'lesson');
1971 }
1972 }
1973
1974 if ($result->response) {
1975 if ($this->lesson->review && !$result->correctanswer && !$result->isessayquestion) {
1976 $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
1977 $qattempts = $DB->count_records("lesson_attempts", array("userid"=>$USER->id, "retry"=>$nretakes, "pageid"=>$this->properties->id));
1978 if ($qattempts == 1) {
1979 $result->feedback = $OUTPUT->box(get_string("firstwrong", "lesson"), 'feedback');
1980 } else {
1981 $result->feedback = $OUTPUT->BOX(get_string("secondpluswrong", "lesson"), 'feedback');
1982 }
1983 } else {
1984 $class = 'response';
1985 if ($result->correctanswer) {
1986 $class .= ' correct'; //CSS over-ride this if they exist (!important)
1987 } else if (!$result->isessayquestion) {
1988 $class .= ' incorrect'; //CSS over-ride this if they exist (!important)
1989 }
1990 $options = new stdClass;
1991 $options->noclean = true;
1992 $options->para = true;
1993 $result->feedback = $OUTPUT->box(format_text($this->properties->contents, $this->properties->contentsformat, $options), 'generalbox boxaligncenter');
1994 $result->feedback .= '<div class="correctanswer generalbox"><em>'.get_string("youranswer", "lesson").'</em> : '.$result->studentanswer; // already in clean html
1995 $result->feedback .= $OUTPUT->box($result->response, $class); // already conerted to HTML
1996 echo "</div>";
1997 }
1998 }
1999 }
2000
2001 return $result;
2002 }
2003
2004 /**
2005 * Returns the string for a jump name
2006 *
2007 * @final
2008 * @param int $jumpto Jump code or page ID
2009 * @return string
2010 **/
2011 final protected function get_jump_name($jumpto) {
2012 global $DB;
2013 static $jumpnames = array();
2014
2015 if (!array_key_exists($jumpto, $jumpnames)) {
2016 if ($jumpto == 0) {
2017 $jumptitle = get_string('thispage', 'lesson');
2018 } elseif ($jumpto == LESSON_NEXTPAGE) {
2019 $jumptitle = get_string('nextpage', 'lesson');
2020 } elseif ($jumpto == LESSON_EOL) {
2021 $jumptitle = get_string('endoflesson', 'lesson');
2022 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
2023 $jumptitle = get_string('unseenpageinbranch', 'lesson');
2024 } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
2025 $jumptitle = get_string('previouspage', 'lesson');
2026 } elseif ($jumpto == LESSON_RANDOMPAGE) {
2027 $jumptitle = get_string('randompageinbranch', 'lesson');
2028 } elseif ($jumpto == LESSON_RANDOMBRANCH) {
2029 $jumptitle = get_string('randombranch', 'lesson');
2030 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
2031 $jumptitle = get_string('clusterjump', 'lesson');
2032 } else {
2033 if (!$jumptitle = $DB->get_field('lesson_pages', 'title', array('id' => $jumpto))) {
2034 $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
2035 }
2036 }
2037 $jumpnames[$jumpto] = format_string($jumptitle,true);
2038 }
2039
2040 return $jumpnames[$jumpto];
2041 }
2042
2043 /**
ff85f902 2044 * Constructor method
1e7f8ea2
PS
2045 * @param object $properties
2046 * @param lesson $lesson
2047 */
2048 public function __construct($properties, lesson $lesson) {
2049 parent::__construct($properties);
2050 $this->lesson = $lesson;
2051 }
2052
2053 /**
2054 * Returns the score for the attempt
ff85f902 2055 * This may be overridden by page types that require manual grading
1e7f8ea2
PS
2056 * @param array $answers
2057 * @param object $attempt
2058 * @return int
2059 */
2060 public function earned_score($answers, $attempt) {
2061 return $answers[$attempt->answerid]->score;
2062 }
2063
2064 /**
2065 * This is a callback method that can be override and gets called when ever a page
2066 * is viewed
2067 *
2068 * @param bool $canmanage True if the user has the manage cap
2069 * @return mixed
2070 */
2071 public function callback_on_view($canmanage) {
2072 return true;
2073 }
2074
2075 /**
2076 * Updates a lesson page and its answers within the database
2077 *
2078 * @param object $properties
2079 * @return bool
2080 */
2081 public function update($properties, $context = null, $maxbytes = null) {
2082 global $DB;
2083 $answers = $this->get_answers();
2084 $properties->id = $this->properties->id;
2085 $properties->lessonid = $this->lesson->id;
2086 if (empty($properties->qoption)) {
2087 $properties->qoption = '0';
2088 }
2089 if (empty($context)) {
2090 $context = $PAGE->context;
2091 }
2092 if ($maxbytes === null) {
2093 $maxbytes =get_max_upload_file_size();
2094 }
2095 $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $properties->id);
2096 $DB->update_record("lesson_pages", $properties);
2097
2098 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
2099 if (!array_key_exists($i, $this->answers)) {
2100 $this->answers[$i] = new stdClass;
2101 $this->answers[$i]->lessonid = $this->lesson->id;
2102 $this->answers[$i]->pageid = $this->id;
2103 $this->answers[$i]->timecreated = $this->timecreated;
2104 }
2105 if (!empty($properties->answer_editor[$i])) {
2106 $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
2107 $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
2108 if (isset($properties->response_editor[$i])) {
2109 $this->answers[$i]->response = $properties->response_editor[$i]['text'];
2110 $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
2111 }
2112 if (isset($properties->jumpto[$i])) {
2113 $this->answers[$i]->jumpto = $properties->jumpto[$i];
2114 }
2115 if ($this->lesson->custom && isset($properties->score[$i])) {
2116 $this->answers[$i]->score = $properties->score[$i];
2117 }
2118 if (!isset($this->answers[$i]->id)) {
2119 $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
2120 } else {
2121 $DB->update_record("lesson_answers", $this->answers[$i]->properties());
2122 }
2123
2124 } else {
2125 break;
2126 }
2127 }
2128 return true;
2129 }
2130
2131 /**
2132 * Can be set to true if the page requires a static link to create a new instance
2133 * instead of simply being included in the dropdown
2134 * @param int $previd
2135 * @return bool
2136 */
2137 public function add_page_link($previd) {
2138 return false;
2139 }
2140
2141 /**
2142 * Returns true if a page has been viewed before
2143 *
2144 * @param array|int $param Either an array of pages that have been seen or the
2145 * number of retakes a user has had
2146 * @return bool
2147 */
2148 public function is_unseen($param) {
2149 global $USER, $DB;
2150 if (is_array($param)) {
2151 $seenpages = $param;
2152 return (!array_key_exists($this->properties->id, $seenpages));
2153 } else {
2154 $nretakes = $param;
2155 if (!$DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
2156 return true;
2157 }
2158 }
2159 return false;
2160 }
2161
2162 /**
2163 * Checks to see if a page has been answered previously
2164 * @param int $nretakes
2165 * @return bool
2166 */
2167 public function is_unanswered($nretakes) {
2168 global $DB, $USER;
2169 if (!$DB->count_records("lesson_attempts", array('pageid'=>$this->properties->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
2170 return true;
2171 }
2172 return false;
2173 }
2174
2175 /**
2176 * Creates answers within the database for this lesson_page. Usually only ever
2177 * called when creating a new page instance
2178 * @param object $properties
2179 * @return array
2180 */
2181 public function create_answers($properties) {
2182 global $DB;
2183 // now add the answers
2184 $newanswer = new stdClass;
2185 $newanswer->lessonid = $this->lesson->id;
2186 $newanswer->pageid = $this->properties->id;
2187 $newanswer->timecreated = $this->properties->timecreated;
2188
2189 $answers = array();
2190
2191 for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
2192 $answer = clone($newanswer);
2193 if (!empty($properties->answer_editor[$i])) {
2194 $answer->answer = $properties->answer_editor[$i]['text'];
2195 $answer->answerformat = $properties->answer_editor[$i]['format'];
2196 if (isset($properties->response_editor[$i])) {
2197 $answer->response = $properties->response_editor[$i]['text'];
2198 $answer->responseformat = $properties->response_editor[$i]['format'];
2199 }
2200 if (isset($properties->jumpto[$i])) {
2201 $answer->jumpto = $properties->jumpto[$i];
2202 }
2203 if ($this->lesson->custom && isset($properties->score[$i])) {
2204 $answer->score = $properties->score[$i];
2205 }
2206 $answer->id = $DB->insert_record("lesson_answers", $answer);
2207 $answers[$answer->id] = new lesson_page_answer($answer);
2208 } else {
2209 break;
2210 }
2211 }
2212
2213 $this->answers = $answers;
2214 return $answers;
2215 }
2216
2217 /**
ff85f902 2218 * This method MUST be overridden by all question page types, or page types that
1e7f8ea2
PS
2219 * wish to score a page.
2220 *
2221 * The structure of result should always be the same so it is a good idea when
2222 * overriding this method on a page type to call
2223 * <code>
2224 * $result = parent::check_answer();
2225 * </code>
ff85f902 2226 * before modifying it as required.
1e7f8ea2
PS
2227 *
2228 * @return stdClass
2229 */
2230 public function check_answer() {
2231 $result = new stdClass;
2232 $result->answerid = 0;
2233 $result->noanswer = false;
2234 $result->correctanswer = false;
2235 $result->isessayquestion = false; // use this to turn off review button on essay questions
2236 $result->response = '';
2237 $result->newpageid = 0; // stay on the page
2238 $result->studentanswer = ''; // use this to store student's answer(s) in order to display it on feedback page
2239 $result->userresponse = null;
2240 $result->feedback = '';
2241 $result->nodefaultresponse = false; // Flag for redirecting when default feedback is turned off
2242 return $result;
2243 }
2244
2245 /**
2246 * True if the page uses a custom option
2247 *
2248 * Should be override and set to true if the page uses a custom option.
2249 *
2250 * @return bool
2251 */
2252 public function has_option() {
2253 return false;
2254 }
2255
2256 /**
2257 * Returns the maximum number of answers for this page given the maximum number
2258 * of answers permitted by the lesson.
2259 *
2260 * @param int $default
2261 * @return int
2262 */
2263 public function max_answers($default) {
2264 return $default;
2265 }
2266
2267 /**
2268 * Returns the properties of this lesson page as an object
2269 * @return stdClass;
2270 */
2271 public function properties() {
2272 $properties = clone($this->properties);
2273 if ($this->answers === null) {
2274 $this->get_answers();
2275 }
2276 if (count($this->answers)>0) {
2277 $count = 0;
2278 foreach ($this->answers as $answer) {
2279 $properties->{'answer_editor['.$count.']'} = array('text'=>$answer->answer, 'format'=>$answer->answerformat);
2280 $properties->{'response_editor['.$count.']'} = array('text'=>$answer->response, 'format'=>$answer->responseformat);
2281 $properties->{'jumpto['.$count.']'} = $answer->jumpto;
2282 $properties->{'score['.$count.']'} = $answer->score;
2283 $count++;
2284 }
2285 }
2286 return $properties;
2287 }
2288
2289 /**
ff85f902 2290 * Returns an array of options to display when choosing the jumpto for a page/answer
1e7f8ea2
PS
2291 * @static
2292 * @param int $pageid
2293 * @param lesson $lesson
2294 * @return array
2295 */
2296 public static function get_jumptooptions($pageid, lesson $lesson) {
2297 global $DB;
2298 $jump = array();
2299 $jump[0] = get_string("thispage", "lesson");
2300 $jump[LESSON_NEXTPAGE] = get_string("nextpage", "lesson");
2301 $jump[LESSON_PREVIOUSPAGE] = get_string("previouspage", "lesson");
2302 $jump[LESSON_EOL] = get_string("endoflesson", "lesson");
2303
2304 if ($pageid == 0) {
2305 return $jump;
2306 }
2307
2308 $pages = $lesson->load_all_pages();
2309 if ($pages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_BRANCHTABLE), array(LESSON_PAGE_ENDOFBRANCH, LESSON_PAGE_CLUSTER))) {
2310 $jump[LESSON_UNSEENBRANCHPAGE] = get_string("unseenpageinbranch", "lesson");
2311 $jump[LESSON_RANDOMPAGE] = get_string("randompageinbranch", "lesson");
2312 }
2313 if($pages[$pageid]->qtype == LESSON_PAGE_CLUSTER || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_CLUSTER), array(LESSON_PAGE_ENDOFCLUSTER))) {
2314 $jump[LESSON_CLUSTERJUMP] = get_string("clusterjump", "lesson");
2315 }
2316 if (!optional_param('firstpage', 0, PARAM_INT)) {
2317 $apageid = $DB->get_field("lesson_pages", "id", array("lessonid" => $lesson->id, "prevpageid" => 0));
2318 while (true) {
2319 if ($apageid) {
2320 $title = $DB->get_field("lesson_pages", "title", array("id" => $apageid));
2321 $jump[$apageid] = strip_tags(format_string($title,true));
2322 $apageid = $DB->get_field("lesson_pages", "nextpageid", array("id" => $apageid));
2323 } else {
2324 // last page reached
2325 break;
2326 }
2327 }
2328 }
2329 return $jump;
2330 }
2331 /**
2332 * Returns the contents field for the page properly formatted and with plugin
2333 * file url's converted
2334 * @return string
2335 */
2336 public function get_contents() {
2337 global $PAGE;
2338 if (!empty($this->properties->contents)) {
2339 if (!isset($this->properties->contentsformat)) {
2340 $this->properties->contentsformat = FORMAT_HTML;
2341 }
2342 $context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->id);
2343 return file_rewrite_pluginfile_urls($this->properties->contents, 'pluginfile.php', $context->id, 'mod_lesson', 'page_contents', $this->properties->id);
2344 } else {
2345 return '';
2346 }
2347 }
2348
2349 /**
2350 * Set to true if this page should display in the menu block
2351 * @return bool
2352 */
2353 protected function get_displayinmenublock() {
2354 return false;
2355 }
2356
2357 /**
2358 * Get the string that describes the options of this page type
2359 * @return string
2360 */
2361 public function option_description_string() {
2362 return '';
2363 }
2364
2365 /**
2366 * Updates a table with the answers for this page
2367 * @param html_table $table
2368 * @return html_table
2369 */
2370 public function display_answers(html_table $table) {
2371 $answers = $this->get_answers();
2372 $i = 1;
2373 foreach ($answers as $answer) {
2374 $cells = array();
2375 $cells[] = "<span class=\"label\">".get_string("jump", "lesson")." $i<span>: ";
2376 $cells[] = $this->get_jump_name($answer->jumpto);
2377 $table->data[] = new html_table_row($cells);
2378 if ($i === 1){
2379 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
2380 }
2381 $i++;
2382 }
2383 return $table;
2384 }
2385
2386 /**
2387 * Determines if this page should be grayed out on the management/report screens
2388 * @return int 0 or 1
2389 */
2390 protected function get_grayout() {
2391 return 0;
2392 }
2393
2394 /**
2395 * Adds stats for this page to the &pagestats object. This should be defined
2396 * for all page types that grade
2397 * @param array $pagestats
2398 * @param int $tries
2399 * @return bool
2400 */
2401 public function stats(array &$pagestats, $tries) {
2402 return true;
2403 }
2404
2405 /**
2406 * Formats the answers of this page for a report
2407 *
2408 * @param object $answerpage
2409 * @param object $answerdata
2410 * @param object $useranswer
2411 * @param array $pagestats
2412 * @param int $i Count of first level answers
2413 * @param int $n Count of second level answers
2414 * @return object The answer page for this
2415 */
2416 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
2417 $answers = $this->get_answers();
2418 $formattextdefoptions = new stdClass;
2419 $formattextdefoptions->para = false; //I'll use it widely in this page
2420 foreach ($answers as $answer) {
2421 $data = get_string('jumpsto', 'lesson', $this->get_jump_name($answer->jumpto));
2422 $answerdata->answers[] = array($data, "");
2423 $answerpage->answerdata = $answerdata;
2424 }
2425 return $answerpage;
2426 }
2427
2428 /**
2429 * Gets an array of the jumps used by the answers of this page
2430 *
2431 * @return array
2432 */
2433 public function get_jumps() {
2434 global $DB;
2435 $jumps = array();
2436 $params = array ("lessonid" => $this->lesson->id, "pageid" => $this->properties->id);
2437 if ($answers = $this->get_answers()) {
2438 foreach ($answers as $answer) {
2439 $jumps[] = $this->get_jump_name($answer->jumpto);
2440 }
2441 }
2442 return $jumps;
2443 }
2444 /**
2445 * Informs whether this page type require manual grading or not
2446 * @return bool
2447 */
2448 public function requires_manual_grading() {
2449 return false;
2450 }
2451
2452 /**
2453 * A callback method that allows a page to override the next page a user will
2454 * see during when this page is being completed.
2455 * @return false|int
2456 */
2457 public function override_next_page() {
2458 return false;
2459 }
2460
2461 /**
2462 * This method is used to determine if this page is a valid page
2463 *
2464 * @param array $validpages
2465 * @param array $pageviews
2466 * @return int The next page id to check
2467 */
2468 public function valid_page_and_view(&$validpages, &$pageviews) {
2469 $validpages[$this->properties->id] = 1;
2470 return $this->properties->nextpageid;
2471 }
2472}
2473
2474
2475
2476/**
2477 * Class used to represent an answer to a page
2478 *
2479 * @property int $id The ID of this answer in the database
2480 * @property int $lessonid The ID of the lesson this answer belongs to
2481 * @property int $pageid The ID of the page this answer belongs to
2482 * @property int $jumpto Identifies where the user goes upon completing a page with this answer
2483 * @property int $grade The grade this answer is worth
2484 * @property int $score The score this answer will give
2485 * @property int $flags Used to store options for the answer
2486 * @property int $timecreated A timestamp of when the answer was created
2487 * @property int $timemodified A timestamp of when the answer was modified
2488 * @property string $answer The answer itself
2489 * @property string $response The response the user sees if selecting this answer
2490 *
2491 * @copyright 2009 Sam Hemelryk
2492 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2493 */
2494class lesson_page_answer extends lesson_base {
2495
2496 /**
2497 * Loads an page answer from the DB
2498 *
2499 * @param int $id
2500 * @return lesson_page_answer
2501 */
2502 public static function load($id) {
2503 global $DB;
2504 $answer = $DB->get_record("lesson_answers", array("id" => $id));
2505 return new lesson_page_answer($answer);
2506 }
2507
2508 /**
2509 * Given an object of properties and a page created answer(s) and saves them
2510 * in the database.
2511 *
2512 * @param stdClass $properties
2513 * @param lesson_page $page
2514 * @return array
2515 */
2516 public static function create($properties, lesson_page $page) {
2517 return $page->create_answers($properties);
2518 }
2519
2520}
2521
2522/**
2523 * A management class for page types
2524 *
2525 * This class is responsible for managing the different pages. A manager object can
2526 * be retrieved by calling the following line of code:
2527 * <code>
2528 * $manager = lesson_page_type_manager::get($lesson);
2529 * </code>
2530 * The first time the page type manager is retrieved the it includes all of the
2531 * different page types located in mod/lesson/pagetypes.
2532 *
2533 * @copyright 2009 Sam Hemelryk
2534 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2535 */
2536class lesson_page_type_manager {
2537
2538 /**
2539 * An array of different page type classes
2540 * @var array
2541 */
2542 protected $types = array();
2543
2544 /**
2545 * Retrieves the lesson page type manager object
2546 *
2547 * If the object hasn't yet been created it is created here.
2548 *
2549 * @staticvar lesson_page_type_manager $pagetypemanager
2550 * @param lesson $lesson
2551 * @return lesson_page_type_manager
2552 */
2553 public static function get(lesson $lesson) {
2554 static $pagetypemanager;
2555 if (!($pagetypemanager instanceof lesson_page_type_manager)) {
2556 $pagetypemanager = new lesson_page_type_manager();
2557 $pagetypemanager->load_lesson_types($lesson);
2558 }
2559 return $pagetypemanager;
2560 }
2561
2562 /**
2563 * Finds and loads all lesson page types in mod/lesson/pagetypes
2564 *
2565 * @param lesson $lesson
2566 */
2567 public function load_lesson_types(lesson $lesson) {
2568 global $CFG;
2569 $basedir = $CFG->dirroot.'/mod/lesson/pagetypes/';
2570 $dir = dir($basedir);
2571 while (false !== ($entry = $dir->read())) {
2572 if (strpos($entry, '.')===0 || !preg_match('#^[a-zA-Z]+\.php#i', $entry)) {
2573 continue;
2574 }
2575 require_once($basedir.$entry);
2576 $class = 'lesson_page_type_'.strtok($entry,'.');
2577 if (class_exists($class)) {
2578 $pagetype = new $class(new stdClass, $lesson);
2579 $this->types[$pagetype->typeid] = $pagetype;
2580 }
2581 }
2582
2583 }
2584
2585 /**
2586 * Returns an array of strings to describe the loaded page types
2587 *
2588 * @param int $type Can be used to return JUST the string for the requested type
2589 * @return array
2590 */
2591 public function get_page_type_strings($type=null, $special=true) {
2592 $types = array();
2593 foreach ($this->types as $pagetype) {
2594 if (($type===null || $pagetype->type===$type) && ($special===true || $pagetype->is_standard())) {
2595 $types[$pagetype->typeid] = $pagetype->typestring;
2596 }
2597 }
2598 return $types;
2599 }
2600
2601 /**
2602 * Returns the basic string used to identify a page type provided with an id
2603 *
2604 * This string can be used to instantiate or identify the page type class.
2605 * If the page type id is unknown then 'unknown' is returned
2606 *
2607 * @param int $id
2608 * @return string
2609 */
2610 public function get_page_type_idstring($id) {
2611 foreach ($this->types as $pagetype) {
2612 if ((int)$pagetype->typeid === (int)$id) {
2613 return $pagetype->idstring;
2614 }
2615 }
2616 return 'unknown';
2617 }
2618
2619 /**
2620 * Loads a page for the provided lesson given it's id
2621 *
2622 * This function loads a page from the lesson when given both the lesson it belongs
2623 * to as well as the page's id.
2624 * If the page doesn't exist an error is thrown
2625 *
2626 * @param int $pageid The id of the page to load
2627 * @param lesson $lesson The lesson the page belongs to
2628 * @return lesson_page A class that extends lesson_page
2629 */
2630 public function load_page($pageid, lesson $lesson) {
2631 global $DB;
2632 if (!($page =$DB->get_record('lesson_pages', array('id'=>$pageid, 'lessonid'=>$lesson->id)))) {
2633 print_error('cannotfindpages', 'lesson');
2634 }
2635 $pagetype = get_class($this->types[$page->qtype]);
2636 $page = new $pagetype($page, $lesson);
2637 return $page;
2638 }
2639
2640 /**
2641 * This function loads ALL pages that belong to the lesson.
2642 *
2643 * @param lesson $lesson
2644 * @return array An array of lesson_page_type_*
2645 */
2646 public function load_all_pages(lesson $lesson) {
2647 global $DB;
2648 if (!($pages =$DB->get_records('lesson_pages', array('lessonid'=>$lesson->id)))) {
2649 print_error('cannotfindpages', 'lesson');
2650 }
2651 foreach ($pages as $key=>$page) {
2652 $pagetype = get_class($this->types[$page->qtype]);
2653 $pages[$key] = new $pagetype($page, $lesson);
2654 }
2655
2656 $orderedpages = array();
2657 $lastpageid = 0;
2658
2659 while (true) {
2660 foreach ($pages as $page) {
2661 if ((int)$page->prevpageid === (int)$lastpageid) {
2662 $orderedpages[$page->id] = $page;
2663 unset($pages[$page->id]);
2664 $lastpageid = $page->id;
2665 if ((int)$page->nextpageid===0) {
2666 break 2;
2667 } else {
2668 break 1;
2669 }
2670 }
2671 }
2672 }
2673
2674 return $orderedpages;
2675 }
2676
2677 /**
ff85f902 2678 * Fetches an mform that can be used to create/edit an page
1e7f8ea2
PS
2679 *
2680 * @param int $type The id for the page type
2681 * @param array $arguments Any arguments to pass to the mform
2682 * @return lesson_add_page_form_base
2683 */
2684 public function get_page_form($type, $arguments) {
2685 $class = 'lesson_add_page_form_'.$this->get_page_type_idstring($type);
2686 if (!class_exists($class) || get_parent_class($class)!=='lesson_add_page_form_base') {
2687 debugging('Lesson page type unknown class requested '.$class, DEBUG_DEVELOPER);
2688 $class = 'lesson_add_page_form_selection';
2689 } else if ($class === 'lesson_add_page_form_unknown') {
2690 $class = 'lesson_add_page_form_selection';
2691 }
2692 return new $class(null, $arguments);
2693 }
2694
2695 /**
2696 * Returns an array of links to use as add page links
2697 * @param int $previd The id of the previous page
2698 * @return array
2699 */
2700 public function get_add_page_type_links($previd) {
2701 global $OUTPUT;
2702
2703 $links = array();
2704
2705 foreach ($this->types as $key=>$type) {
2706 if ($link = $type->add_page_link($previd)) {
2707 $links[$key] = $link;
2708 }
2709 }
2710
2711 return $links;
2712 }
2713}