MDL-7948 some more messaging accessiblity fixes
[moodle.git] / mod / lesson / locallib.php
CommitLineData
9fcf51d9 1<?php // $Id$
5491947a 2/**
3 * Local library file for Lesson. These are non-standard functions that are used
4 * only by Lesson.
5 *
6 * @version $Id$
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package lesson
9 **/
4b55d2af 10
11/**
12* Next page -> any page not seen before
13*/
5e7856af 14if (!defined("LESSON_UNSEENPAGE")) {
ac8e16be 15 define("LESSON_UNSEENPAGE", 1); // Next page -> any page not seen before
4b55d2af 16}
17/**
18* Next page -> any page not answered correctly
19*/
5e7856af 20if (!defined("LESSON_UNANSWEREDPAGE")) {
ac8e16be 21 define("LESSON_UNANSWEREDPAGE", 2); // Next page -> any page not answered correctly
4b55d2af 22}
5e7856af 23
4b55d2af 24/**
25* Define different lesson flows for next page
26*/
5e7856af 27$LESSON_NEXTPAGE_ACTION = array (0 => get_string("normal", "lesson"),
28 LESSON_UNSEENPAGE => get_string("showanunseenpage", "lesson"),
29 LESSON_UNANSWEREDPAGE => get_string("showanunansweredpage", "lesson") );
30
4b55d2af 31// Lesson jump types defined
32// TODO: instead of using define statements, create an array with all the jump values
5e7856af 33
4b55d2af 34/**
35 * Jump to Next Page
36 */
5e7856af 37if (!defined("LESSON_NEXTPAGE")) {
4b55d2af 38 define("LESSON_NEXTPAGE", -1);
39}
40/**
41 * End of Lesson
42 */
5e7856af 43if (!defined("LESSON_EOL")) {
4b55d2af 44 define("LESSON_EOL", -9);
45}
46/**
47 * Jump to an unseen page within a branch and end of branch or end of lesson
48 */
5e7856af 49if (!defined("LESSON_UNSEENBRANCHPAGE")) {
4b55d2af 50 define("LESSON_UNSEENBRANCHPAGE", -50);
51}
52/**
53 * Jump to Previous Page
54 */
5e7856af 55if (!defined("LESSON_PREVIOUSPAGE")) {
4b55d2af 56 define("LESSON_PREVIOUSPAGE", -40);
57}
58/**
59 * Jump to a random page within a branch and end of branch or end of lesson
60 */
5e7856af 61if (!defined("LESSON_RANDOMPAGE")) {
4b55d2af 62 define("LESSON_RANDOMPAGE", -60);
63}
64/**
65 * Jump to a random Branch
66 */
5e7856af 67if (!defined("LESSON_RANDOMBRANCH")) {
4b55d2af 68 define("LESSON_RANDOMBRANCH", -70);
69}
70/**
71 * Cluster Jump
72 */
5e7856af 73if (!defined("LESSON_CLUSTERJUMP")) {
4b55d2af 74 define("LESSON_CLUSTERJUMP", -80);
75}
76/**
77 * Undefined
78 */
5e7856af 79if (!defined("LESSON_UNDEFINED")) {
4b55d2af 80 define("LESSON_UNDEFINED", -99);
81}
82
83// Lesson question types defined
5e7856af 84
4b55d2af 85/**
86 * Short answer question type
87 */
5e7856af 88if (!defined("LESSON_SHORTANSWER")) {
89 define("LESSON_SHORTANSWER", "1");
90}
4b55d2af 91/**
92 * True/False question type
93 */
5e7856af 94if (!defined("LESSON_TRUEFALSE")) {
95 define("LESSON_TRUEFALSE", "2");
96}
4b55d2af 97/**
98 * Multichoice question type
99 *
100 * If you change the value of this then you need
101 * to change it in restorelib.php as well.
102 */
103if (!defined("LESSON_MULTICHOICE")) {
5e7856af 104 define("LESSON_MULTICHOICE", "3");
105}
4b55d2af 106/**
107 * Random question type - not used
108 */
5e7856af 109if (!defined("LESSON_RANDOM")) {
110 define("LESSON_RANDOM", "4");
111}
4b55d2af 112/**
113 * Matching question type
114 *
115 * If you change the value of this then you need
116 * to change it in restorelib.php, in mysql.php
117 * and postgres7.php as well.
118 */
119if (!defined("LESSON_MATCHING")) {
b9869082 120 define("LESSON_MATCHING", "5");
5e7856af 121}
4b55d2af 122/**
123 * Not sure - not used
124 */
5e7856af 125if (!defined("LESSON_RANDOMSAMATCH")) {
126 define("LESSON_RANDOMSAMATCH", "6");
127}
4b55d2af 128/**
129 * Not sure - not used
130 */
5e7856af 131if (!defined("LESSON_DESCRIPTION")) {
132 define("LESSON_DESCRIPTION", "7");
133}
4b55d2af 134/**
135 * Numerical question type
136 */
5e7856af 137if (!defined("LESSON_NUMERICAL")) {
138 define("LESSON_NUMERICAL", "8");
139}
4b55d2af 140/**
141 * Multichoice with multianswer question type
142 */
5e7856af 143if (!defined("LESSON_MULTIANSWER")) {
144 define("LESSON_MULTIANSWER", "9");
145}
4b55d2af 146/**
147 * Essay question type
148 */
5e7856af 149if (!defined("LESSON_ESSAY")) {
ac8e16be 150 define("LESSON_ESSAY", "10");
5e7856af 151}
5e7856af 152
4b55d2af 153/**
154 * Lesson question type array.
155 * Contains all question types used
156 */
5e7856af 157$LESSON_QUESTION_TYPE = array ( LESSON_MULTICHOICE => get_string("multichoice", "quiz"),
158 LESSON_TRUEFALSE => get_string("truefalse", "quiz"),
159 LESSON_SHORTANSWER => get_string("shortanswer", "quiz"),
160 LESSON_NUMERICAL => get_string("numerical", "quiz"),
161 LESSON_MATCHING => get_string("match", "quiz"),
62eda6ea 162 LESSON_ESSAY => get_string("essay", "lesson")
5e7856af 163// LESSON_DESCRIPTION => get_string("description", "quiz"),
164// LESSON_RANDOM => get_string("random", "quiz"),
165// LESSON_RANDOMSAMATCH => get_string("randomsamatch", "quiz"),
166// LESSON_MULTIANSWER => get_string("multianswer", "quiz"),
167 );
168
4b55d2af 169// Non-question page types
170
171/**
172 * Branch Table page
173 */
5e7856af 174if (!defined("LESSON_BRANCHTABLE")) {
175 define("LESSON_BRANCHTABLE", "20");
176}
4b55d2af 177/**
178 * End of Branch page
179 */
5e7856af 180if (!defined("LESSON_ENDOFBRANCH")) {
181 define("LESSON_ENDOFBRANCH", "21");
182}
4b55d2af 183/**
184 * Start of Cluster page
185 */
186if (!defined("LESSON_CLUSTER")) {
187 define("LESSON_CLUSTER", "30");
188}
189/**
190 * End of Cluster page
191 */
192if (!defined("LESSON_ENDOFCLUSTER")) {
193 define("LESSON_ENDOFCLUSTER", "31");
194}
195
196// other variables...
5e7856af 197
4b55d2af 198/**
199 * Flag for the editor for the answer textarea.
200 */
5e7856af 201if (!defined("LESSON_ANSWER_EDITOR")) {
202 define("LESSON_ANSWER_EDITOR", "1");
203}
4b55d2af 204/**
205 * Flag for the editor for the response textarea.
206 */
5e7856af 207if (!defined("LESSON_RESPONSE_EDITOR")) {
208 define("LESSON_RESPONSE_EDITOR", "2");
209}
210
211//////////////////////////////////////////////////////////////////////////////////////
212/// Any other lesson functions go here. Each of them must have a name that
213/// starts with lesson_
214
9fcf51d9 215/**
216 * Print the standard header for lesson module
217 *
68a1e8fb 218 * @uses $CFG
219 * @uses $USER
9fcf51d9 220 * @param object $cm Course module record object
221 * @param object $course Couse record object
222 * @param object $lesson Lesson module record object
223 * @param string $currenttab Current tab for the lesson tabs
68a1e8fb 224 * @return boolean
9fcf51d9 225 **/
d1b025d0 226function lesson_print_header($cm, $course, $lesson, $currenttab = '') {
9fcf51d9 227 global $CFG, $USER;
fb992d71 228
229 $strlessons = get_string('modulenameplural', 'lesson');
230 $strlesson = get_string('modulename', 'lesson');
231 $strname = format_string($lesson->name, true);
232
6e1ff5c8 233 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
9fcf51d9 234
fb992d71 235 // Changed the update_module_button and added another button when a teacher is checking the navigation of the lesson
236 if (has_capability('mod/lesson:edit', $context)) {
237
238 $button = update_module_button($cm->id, $course->id, $strlesson);
239
240 if ($currenttab == 'view') {
241 if (!$pageid = optional_param('pageid', 0, PARAM_INT)) {
242 $pageid = get_field('lesson_pages', 'id', 'lessonid', $lesson->id, 'prevpageid', 0);
243 }
244 if (!empty($pageid) and $pageid != LESSON_EOL) {
245 $button = '<table><tr><td>'.$button.
246 '</td><td>'.
247 '<form target="'. $CFG->framename .'" method="get" action="'. $CFG->wwwroot .'/mod/lesson/lesson.php">'.
248 '<input type="hidden" name="id" value="'. $cm->id .'" />'.
249 '<input type="hidden" name="action" value="editpage" />'.
250 '<input type="hidden" name="redirect" value="navigation" />'.
251 '<input type="hidden" name="pageid" value="'. $pageid .'" />'.
252 '<input type="submit" value="'. get_string('editpagecontent', 'lesson') .'" /></form>
253 </td></tr></table>';
254 }
255 }
256 } else {
257 $button = '';
258 }
259
5631925a 260 if (!optional_param('pageid', 0, PARAM_INT) and !empty($lesson->mediafile)) {
261 // open our pop-up
262 $url = '/mod/lesson/mediafile.php?id='.$cm->id;
263 $name = 'lessonmediafile';
264 $options = 'menubar=0,location=0,left=5,top=5,scrollbars,resizable,width='. $lesson->mediawidth .',height='. $lesson->mediaheight;
265 $meta = "\n<script language=\"javascript\" type=\"text/javascript\">";
266 $meta .= "\n<!--\n";
267 $meta .= " openpopup('$url', '$name', '$options', 0);";
268 $meta .= "\n-->\n";
269 $meta .= '</script>';
270 } else {
271 $meta = '';
272 }
273
9fcf51d9 274/// Header setup
1936c10e 275 if ($course->id != SITEID) {
9fcf51d9 276 $navigation = "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\" title=\"$course->fullname\">$course->shortname</a> ->";
277 } else {
278 $navigation = '';
279 }
280
9fcf51d9 281
282/// Print header, heading, tabs and messages
283 print_header("$course->shortname: $strname", $course->fullname,
284 "$navigation <a href=\"index.php?id=$course->id\" title=\"$strlessons\">$strlessons</a> -> $strname",
5631925a 285 '', $meta, true, $button, navmenu($course, $cm));
9fcf51d9 286
d1b025d0 287 if (has_capability('mod/lesson:manage')) {
9fcf51d9 288 print_heading_with_help(format_string($lesson->name, true), "overview", "lesson");
d1b025d0 289 } else {
290 print_heading($lesson->name);
9fcf51d9 291 }
292
6e1ff5c8 293 if (!empty($currenttab) and has_capability('mod/lesson:manage', $context)) {
9fcf51d9 294 include($CFG->dirroot.'/mod/lesson/tabs.php');
295 }
f15eb92c 296
297 lesson_print_messages();
68a1e8fb 298
299 return true;
9fcf51d9 300}
301
302/**
303 * Returns course module, course and module instance given
304 * either the course module ID or a lesson module ID.
305 *
306 * @param int $cmid Course Module ID
307 * @param int $lessonid Lesson module instance ID
308 * @return array array($cm, $course, $lesson)
309 **/
310function lesson_get_basics($cmid = 0, $lessonid = 0) {
311 if ($cmid) {
f15eb92c 312 if (!$cm = get_coursemodule_from_id('lesson', $cmid)) {
9fcf51d9 313 error('Course Module ID was incorrect');
314 }
315 if (!$course = get_record('course', 'id', $cm->course)) {
316 error('Course is misconfigured');
317 }
318 if (!$lesson = get_record('lesson', 'id', $cm->instance)) {
319 error('Course module is incorrect');
320 }
321 } else if ($lessonid) {
322 if (!$lesson = get_record('lesson', 'id', $lessonid)) {
323 error('Course module is incorrect');
324 }
325 if (!$course = get_record('course', 'id', $lesson->course)) {
326 error('Course is misconfigured');
327 }
328 if (!$cm = get_coursemodule_from_instance('lesson', $lesson->id, $course->id)) {
329 error('Course Module ID was incorrect');
330 }
331 } else {
332 error('No course module ID or lesson ID were passed');
333 }
334
335 return array($cm, $course, $lesson);
336}
337
6e1ff5c8 338/**
339 * Sets a message to be printed. Messages are printed
340 * by calling {@link lesson_print_messages()}.
341 *
342 * @uses $SESSION
343 * @param string $message The message to be printed
344 * @param string $class Class to be passed to {@link notify()}. Usually notifyproblem or notifysuccess.
345 * @param string $align Alignment of the message
346 * @return boolean
347 **/
348function lesson_set_message($message, $class="notifyproblem", $align='center') {
349 global $SESSION;
350
351 if (empty($SESSION->lesson_messages) or !is_array($SESSION->lesson_messages)) {
352 $SESSION->lesson_messages = array();
353 }
354
355 $SESSION->lesson_messages[] = array($message, $class, $align);
356
357 return true;
358}
359
360/**
361 * Print all set messages.
362 *
363 * See {@link lesson_set_message()} for setting messages.
364 *
365 * Uses {@link notify()} to print the messages.
366 *
367 * @uses $SESSION
368 * @return boolean
369 **/
370function lesson_print_messages() {
371 global $SESSION;
372
373 if (empty($SESSION->lesson_messages)) {
374 // No messages to print
375 return true;
376 }
377
378 foreach($SESSION->lesson_messages as $message) {
379 notify($message[0], $message[1], $message[2]);
380 }
381
382 // Reset
383 unset($SESSION->lesson_messages);
384
385 return true;
386}
387
259990d2 388/**
389 * Prints a lesson link that submits a form.
390 *
391 * If Javascript is disabled, then a regular submit button is printed
392 *
68a1e8fb 393 * @param string $name Name of the link or button
394 * @param string $form The name of the form to be submitted
395 * @param string $align Alignment of the button
396 * @param string $class Class names to add to the div wrapper
397 * @param string $title Title for the link (Not used if javascript is disabled)
398 * @param string $id ID tag
399 * @param boolean $return Return flag
259990d2 400 * @return mixed boolean/html
401 **/
402function lesson_print_submit_link($name, $form, $align = 'center', $class='standardbutton', $title = '', $id = '', $return = false) {
563106f0 403 if (!empty($align)) {
4cb35c1b 404 $align = " align=\"$align\"";
563106f0 405 }
259990d2 406 if (!empty($id)) {
407 $id = " id=\"$id\"";
408 }
409 if (empty($title)) {
410 $title = $name;
411 }
62bb11d8 412
4cb35c1b 413 $output = "<div class=\"lessonbutton $class\" $align>\n";
37aa59a9 414 $output .= "<input type=\"submit\" value=\"$name\" $align $id />";
259990d2 415 $output .= "</div>\n";
416
2cb24b64 417 if ($return) {
418 return $output;
419 } else {
420 echo $output;
421 return true;
422 }
423}
424
425/**
426 * Prints a time remaining in the following format: H:MM:SS
427 *
428 * @param int $starttime Time when the lesson started
429 * @param int $maxtime Length of the lesson
430 * @param boolean $return Return output switch
431 * @return mixed boolean/string
432 **/
2163c226 433function lesson_print_time_remaining($starttime, $maxtime, $return = false) {
2cb24b64 434 // Calculate hours, minutes and seconds
435 $timeleft = $starttime + $maxtime * 60 - time();
436 $hours = floor($timeleft/3600);
437 $timeleft = $timeleft - ($hours * 3600);
438 $minutes = floor($timeleft/60);
439 $secs = $timeleft - ($minutes * 60);
440
441 if ($minutes < 10) {
442 $minutes = "0$minutes";
443 }
444 if ($secs < 10) {
445 $secs = "0$secs";
446 }
447 $output = array();
448 $output[] = $hours;
449 $output[] = $minutes;
450 $output[] = $secs;
451
452 $output = implode(':', $output);
453
259990d2 454 if ($return) {
455 return $output;
456 } else {
457 echo $output;
458 return true;
459 }
460}
461
68a1e8fb 462/**
463 * Prints the page action buttons
464 *
465 * Move/Edit/Preview/Delete
466 *
467 * @uses $CFG
468 * @param int $cmid Course Module ID
260a56b1 469 * @param object $page Page record
68a1e8fb 470 * @param boolean $printmove Flag to print the move button or not
260a56b1 471 * @param boolean $printaddpage Flag to print the add page drop-down or not
68a1e8fb 472 * @param boolean $return Return flag
473 * @return mixed boolean/string
474 **/
260a56b1 475function lesson_print_page_actions($cmid, $page, $printmove, $printaddpage = false, $return = false) {
68a1e8fb 476 global $CFG;
477
478 $context = get_context_instance(CONTEXT_MODULE, $cmid);
479 $actions = array();
480
481 if (has_capability('mod/lesson:edit', $context)) {
482 if ($printmove) {
2ba9db3c 483 $actions[] = "<a title=\"".get_string('move')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=move&amp;pageid=$page->id\">
68a1e8fb 484 <img src=\"$CFG->pixpath/t/move.gif\" height=\"11\" width=\"11\" alt=\"".get_string('move')."\" border=\"0\" /></a>\n";
485 }
260a56b1 486 $actions[] = "<a title=\"".get_string('update')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=editpage&amp;pageid=$page->id\">
68a1e8fb 487 <img src=\"$CFG->pixpath/t/edit.gif\" height=\"11\" width=\"11\" alt=\"".get_string('update')."\" border=\"0\" /></a>\n";
488
260a56b1 489 $actions[] = "<a title=\"".get_string('preview')."\" href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&amp;pageid=$page->id\">
68a1e8fb 490 <img src=\"$CFG->pixpath/t/preview.gif\" height=\"11\" width=\"11\" alt=\"".get_string('preview')."\" border=\"0\" /></a>\n";
491
260a56b1 492 $actions[] = "<a title=\"".get_string('delete')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=confirmdelete&amp;pageid=$page->id\">
68a1e8fb 493 <img src=\"$CFG->pixpath/t/delete.gif\" height=\"11\" width=\"11\" alt=\"".get_string('delete')."\" border=\"0\" /></a>\n";
494
260a56b1 495 if ($printaddpage) {
496 // Add page drop-down
497 $options = array();
498 $options['addcluster&amp;sesskey='.sesskey()] = get_string('clustertitle', 'lesson');
499 $options['addendofcluster&amp;sesskey='.sesskey()] = get_string('endofclustertitle', 'lesson');
500 $options['addbranchtable'] = get_string('branchtable', 'lesson');
501 $options['addendofbranch&amp;sesskey='.sesskey()] = get_string('endofbranch', 'lesson');
502 $options['addpage'] = get_string('question', 'lesson');
503 // Base url
504 $common = "$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;pageid=$page->id&amp;action=";
505
506 $actions[] = popup_form($common, $options, "addpage_$page->id", '', get_string('addpage', 'lesson').'...', '', '', true);
507 }
68a1e8fb 508 }
509
510 $actions = implode(' ', $actions);
511
512 if ($return) {
513 return $actions;
514 } else {
515 echo $actions;
516 return false;
517 }
518}
519
520/**
521 * Prints the add links in expanded view or single view when editing
522 *
523 * @uses $CFG
524 * @param int $cmid Course Module ID
525 * @param int $prevpageid Previous page id
526 * @param boolean $return Return flag
527 * @return mixed boolean/string
528 * @todo &amp;pageid does not make sense, it is prevpageid
529 **/
530function lesson_print_add_links($cmid, $prevpageid, $return = false) {
531 global $CFG;
532
533 $context = get_context_instance(CONTEXT_MODULE, $cmid);
534
535 $links = '';
536 if (has_capability('mod/lesson:edit', $context)) {
537 $links = array();
538 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/import.php?id=$cmid&amp;pageid=$prevpageid\">".
539 get_string('importquestions', 'lesson').'</a>';
540
541 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addcluster&amp;pageid=$prevpageid\">".
542 get_string('addcluster', 'lesson').'</a>';
543
544 if ($prevpageid != 0) {
545 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofcluster&amp;pageid=$prevpageid\">".
546 get_string('addendofcluster', 'lesson').'</a>';
547 }
548 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addbranchtable&amp;pageid=$prevpageid\">".
549 get_string('addabranchtable', 'lesson').'</a>';
550
551 if ($prevpageid != 0) {
552 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofbranch&amp;pageid=$prevpageid\">".
553 get_string('addanendofbranch', 'lesson').'</a>';
554 }
555
556 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addpage&amp;pageid=$prevpageid\">".
557 get_string('addaquestionpagehere', 'lesson').'</a>';
558
559 $links = implode(" | \n", $links);
560 $links = "\n<div class=\"addlinks\">\n$links\n</div>\n";
561 }
562
563 if ($return) {
564 return $links;
565 } else {
566 echo $links;
567 return true;
568 }
569}
570
571/**
572 * Returns the string for a page type
573 *
574 * @uses $LESSON_QUESTION_TYPE
575 * @param int $qtype Page type
576 * @return string
577 **/
578function lesson_get_qtype_name($qtype) {
579 global $LESSON_QUESTION_TYPE;
580 switch ($qtype) {
581 case LESSON_ESSAY :
582 case LESSON_SHORTANSWER :
583 case LESSON_MULTICHOICE :
584 case LESSON_MATCHING :
585 case LESSON_TRUEFALSE :
586 case LESSON_NUMERICAL :
587 return $LESSON_QUESTION_TYPE[$qtype];
588 break;
589 case LESSON_BRANCHTABLE :
590 return get_string("branchtable", "lesson");
591 break;
592 case LESSON_ENDOFBRANCH :
593 return get_string("endofbranch", "lesson");
594 break;
595 case LESSON_CLUSTER :
596 return get_string("clustertitle", "lesson");
597 break;
598 case LESSON_ENDOFCLUSTER :
599 return get_string("endofclustertitle", "lesson");
600 break;
601 default:
602 return '';
603 break;
604 }
605}
606
607/**
608 * Returns the string for a jump name
609 *
610 * @param int $jumpto Jump code or page ID
611 * @return string
612 **/
613function lesson_get_jump_name($jumpto) {
614 if ($jumpto == 0) {
615 $jumptitle = get_string('thispage', 'lesson');
616 } elseif ($jumpto == LESSON_NEXTPAGE) {
617 $jumptitle = get_string('nextpage', 'lesson');
618 } elseif ($jumpto == LESSON_EOL) {
619 $jumptitle = get_string('endoflesson', 'lesson');
620 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
621 $jumptitle = get_string('unseenpageinbranch', 'lesson');
622 } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
623 $jumptitle = get_string('previouspage', 'lesson');
624 } elseif ($jumpto == LESSON_RANDOMPAGE) {
625 $jumptitle = get_string('randompageinbranch', 'lesson');
626 } elseif ($jumpto == LESSON_RANDOMBRANCH) {
627 $jumptitle = get_string('randombranch', 'lesson');
628 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
629 $jumptitle = get_string('clusterjump', 'lesson');
630 } else {
631 if (!$jumptitle = get_field('lesson_pages', 'title', 'id', $jumpto)) {
632 $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
633 }
634 }
635
636 return format_string($jumptitle,true);
637}
638
4b55d2af 639/**
640 * Given some question info and some data about the the answers
641 * this function parses, organises and saves the question
642 *
643 * This is only used when IMPORTING questions and is only called
644 * from format.php
645 * Lifted from mod/quiz/lib.php -
646 * 1. all reference to oldanswers removed
647 * 2. all reference to quiz_multichoice table removed
648 * 3. In SHORTANSWER questions usecase is store in the qoption field
649 * 4. In NUMERIC questions store the range as two answers
650 * 5. TRUEFALSE options are ignored
651 * 6. For MULTICHOICE questions with more than one answer the qoption field is true
652 *
653 * @param opject $question Contains question data like question, type and answers.
654 * @return object Returns $result->error or $result->notice.
655 **/
5e7856af 656function lesson_save_question_options($question) {
5e7856af 657
658 $timenow = time();
659 switch ($question->qtype) {
660 case LESSON_SHORTANSWER:
661
662 $answers = array();
663 $maxfraction = -1;
664
665 // Insert all the new answers
666 foreach ($question->answer as $key => $dataanswer) {
667 if ($dataanswer != "") {
f7ffb898 668 $answer = new stdClass;
5e7856af 669 $answer->lessonid = $question->lessonid;
670 $answer->pageid = $question->id;
671 if ($question->fraction[$key] >=0.5) {
672 $answer->jumpto = LESSON_NEXTPAGE;
673 }
674 $answer->timecreated = $timenow;
675 $answer->grade = $question->fraction[$key] * 100;
676 $answer->answer = $dataanswer;
c87a341c 677 $answer->response = $question->feedback[$key];
5e7856af 678 if (!$answer->id = insert_record("lesson_answers", $answer)) {
679 $result->error = "Could not insert shortanswer quiz answer!";
680 return $result;
681 }
682 $answers[] = $answer->id;
683 if ($question->fraction[$key] > $maxfraction) {
684 $maxfraction = $question->fraction[$key];
685 }
686 }
687 }
688
689
690 /// Perform sanity checks on fractional grades
691 if ($maxfraction != 1) {
692 $maxfraction = $maxfraction * 100;
693 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
694 return $result;
695 }
696 break;
697
698 case LESSON_NUMERICAL: // Note similarities to SHORTANSWER
699
700 $answers = array();
701 $maxfraction = -1;
702
703
704 // for each answer store the pair of min and max values even if they are the same
705 foreach ($question->answer as $key => $dataanswer) {
706 if ($dataanswer != "") {
f7ffb898 707 $answer = new stdClass;
5e7856af 708 $answer->lessonid = $question->lessonid;
709 $answer->pageid = $question->id;
710 $answer->jumpto = LESSON_NEXTPAGE;
711 $answer->timecreated = $timenow;
712 $answer->grade = $question->fraction[$key] * 100;
dd192b26 713 $min = $question->answer[$key] - $question->tolerance[$key];
714 $max = $question->answer[$key] + $question->tolerance[$key];
715 $answer->answer = $min.":".$max;
716 // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max
5e7856af 717 $answer->response = $question->feedback[$key];
718 if (!$answer->id = insert_record("lesson_answers", $answer)) {
719 $result->error = "Could not insert numerical quiz answer!";
720 return $result;
721 }
722
723 $answers[] = $answer->id;
724 if ($question->fraction[$key] > $maxfraction) {
725 $maxfraction = $question->fraction[$key];
726 }
727 }
728 }
729
730 /// Perform sanity checks on fractional grades
731 if ($maxfraction != 1) {
732 $maxfraction = $maxfraction * 100;
733 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
734 return $result;
735 }
736 break;
737
738
739 case LESSON_TRUEFALSE:
740
741 // the truth
742 $answer->lessonid = $question->lessonid;
743 $answer->pageid = $question->id;
744 $answer->timecreated = $timenow;
745 $answer->answer = get_string("true", "quiz");
746 $answer->grade = $question->answer * 100;
747 if ($answer->grade > 50 ) {
748 $answer->jumpto = LESSON_NEXTPAGE;
749 }
750 if (isset($question->feedbacktrue)) {
751 $answer->response = $question->feedbacktrue;
752 }
753 if (!$true->id = insert_record("lesson_answers", $answer)) {
754 $result->error = "Could not insert quiz answer \"true\")!";
755 return $result;
756 }
757
758 // the lie
f7ffb898 759 $answer = new stdClass;
5e7856af 760 $answer->lessonid = $question->lessonid;
761 $answer->pageid = $question->id;
762 $answer->timecreated = $timenow;
763 $answer->answer = get_string("false", "quiz");
764 $answer->grade = (1 - (int)$question->answer) * 100;
765 if ($answer->grade > 50 ) {
766 $answer->jumpto = LESSON_NEXTPAGE;
767 }
768 if (isset($question->feedbackfalse)) {
769 $answer->response = $question->feedbackfalse;
770 }
771 if (!$false->id = insert_record("lesson_answers", $answer)) {
772 $result->error = "Could not insert quiz answer \"false\")!";
773 return $result;
774 }
775
776 break;
777
778
779 case LESSON_MULTICHOICE:
780
781 $totalfraction = 0;
782 $maxfraction = -1;
783
784 $answers = array();
785
786 // Insert all the new answers
787 foreach ($question->answer as $key => $dataanswer) {
788 if ($dataanswer != "") {
f7ffb898 789 $answer = new stdClass;
5e7856af 790 $answer->lessonid = $question->lessonid;
791 $answer->pageid = $question->id;
792 $answer->timecreated = $timenow;
793 $answer->grade = $question->fraction[$key] * 100;
62eda6ea 794 // changed some defaults
ac8e16be 795 /* Original Code
796 if ($answer->grade > 50 ) {
5e7856af 797 $answer->jumpto = LESSON_NEXTPAGE;
798 }
ac8e16be 799 Replaced with: */
800 if ($answer->grade > 50 ) {
801 $answer->jumpto = LESSON_NEXTPAGE;
5e7856af 802 $answer->score = 1;
803 }
ac8e16be 804 // end Replace
5e7856af 805 $answer->answer = $dataanswer;
806 $answer->response = $question->feedback[$key];
807 if (!$answer->id = insert_record("lesson_answers", $answer)) {
808 $result->error = "Could not insert multichoice quiz answer! ";
809 return $result;
810 }
811 // for Sanity checks
812 if ($question->fraction[$key] > 0) {
813 $totalfraction += $question->fraction[$key];
814 }
815 if ($question->fraction[$key] > $maxfraction) {
816 $maxfraction = $question->fraction[$key];
817 }
818 }
819 }
820
821 /// Perform sanity checks on fractional grades
822 if ($question->single) {
823 if ($maxfraction != 1) {
824 $maxfraction = $maxfraction * 100;
825 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
826 return $result;
827 }
828 } else {
829 $totalfraction = round($totalfraction,2);
830 if ($totalfraction != 1) {
831 $totalfraction = $totalfraction * 100;
832 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
833 return $result;
834 }
835 }
836 break;
837
838 case LESSON_MATCHING:
839
840 $subquestions = array();
841
842 $i = 0;
843 // Insert all the new question+answer pairs
844 foreach ($question->subquestions as $key => $questiontext) {
90455bb3 845 $answertext = $question->subanswers[$key];
5e7856af 846 if (!empty($questiontext) and !empty($answertext)) {
f7ffb898 847 $answer = new stdClass;
5e7856af 848 $answer->lessonid = $question->lessonid;
849 $answer->pageid = $question->id;
850 $answer->timecreated = $timenow;
851 $answer->answer = $questiontext;
852 $answer->response = $answertext;
853 if ($i == 0) {
854 // first answer contains the correct answer jump
855 $answer->jumpto = LESSON_NEXTPAGE;
856 }
857 if (!$subquestion->id = insert_record("lesson_answers", $answer)) {
858 $result->error = "Could not insert quiz match subquestion!";
859 return $result;
860 }
861 $subquestions[] = $subquestion->id;
862 $i++;
863 }
864 }
865
866 if (count($subquestions) < 3) {
867 $result->notice = get_string("notenoughsubquestions", "quiz");
868 return $result;
869 }
870
871 break;
872
873
874 case LESSON_RANDOMSAMATCH:
875 $options->question = $question->id;
876 $options->choose = $question->choose;
877 if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) {
878 $options->id = $existing->id;
879 if (!update_record("quiz_randomsamatch", $options)) {
880 $result->error = "Could not update quiz randomsamatch options!";
881 return $result;
882 }
883 } else {
884 if (!insert_record("quiz_randomsamatch", $options)) {
885 $result->error = "Could not insert quiz randomsamatch options!";
886 return $result;
887 }
888 }
889 break;
890
891 case LESSON_MULTIANSWER:
892 if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
893 $oldmultianswers = array();
894 }
895
896 // Insert all the new multi answers
897 foreach ($question->answers as $dataanswer) {
898 if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
899 $multianswer = $oldmultianswer;
900 $multianswer->positionkey = $dataanswer->positionkey;
901 $multianswer->norm = $dataanswer->norm;
902 $multianswer->answertype = $dataanswer->answertype;
903
904 if (! $multianswer->answers = quiz_save_multianswer_alternatives
905 ($question->id, $dataanswer->answertype,
906 $dataanswer->alternatives, $oldmultianswer->answers))
907 {
908 $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
909 return $result;
910 }
911 if (!update_record("quiz_multianswers", $multianswer)) {
912 $result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
913 return $result;
914 }
915 } else { // This is a completely new answer
f7ffb898 916 $multianswer = new stdClass;
5e7856af 917 $multianswer->question = $question->id;
918 $multianswer->positionkey = $dataanswer->positionkey;
919 $multianswer->norm = $dataanswer->norm;
920 $multianswer->answertype = $dataanswer->answertype;
921
922 if (! $multianswer->answers = quiz_save_multianswer_alternatives
923 ($question->id, $dataanswer->answertype,
924 $dataanswer->alternatives))
925 {
926 $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
927 return $result;
928 }
929 if (!insert_record("quiz_multianswers", $multianswer)) {
930 $result->error = "Could not insert quiz multianswer!";
931 return $result;
932 }
933 }
934 }
935 break;
936
937 case LESSON_RANDOM:
938 break;
939
940 case LESSON_DESCRIPTION:
941 break;
942
943 default:
944 $result->error = "Unsupported question type ($question->qtype)!";
945 return $result;
946 break;
947 }
948 return true;
949}
4b55d2af 950
4b55d2af 951/**
952 * Determins if a jumpto value is correct or not.
953 *
954 * returns true if jumpto page is (logically) after the pageid page or
955 * if the jumpto value is a special value. Returns false in all other cases.
956 *
957 * @param int $pageid Id of the page from which you are jumping from.
958 * @param int $jumpto The jumpto number.
959 * @return boolean True or false after a series of tests.
960 * @todo Can be optimized to only have to make 1 or 2 database calls instead of 1 foreach page in the lesson
961 **/
5e7856af 962function lesson_iscorrect($pageid, $jumpto) {
5e7856af 963
964 // first test the special values
965 if (!$jumpto) {
966 // same page
967 return false;
968 } elseif ($jumpto == LESSON_NEXTPAGE) {
969 return true;
ac8e16be 970 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
5e7856af 971 return true;
972 } elseif ($jumpto == LESSON_RANDOMPAGE) {
973 return true;
974 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
975 return true;
5e7856af 976 } elseif ($jumpto == LESSON_EOL) {
977 return true;
978 }
979 // we have to run through the pages from pageid looking for jumpid
980 $apageid = get_field("lesson_pages", "nextpageid", "id", $pageid);
981 while (true) {
982 if ($jumpto == $apageid) {
983 return true;
984 }
985 if ($apageid) {
986 $apageid = get_field("lesson_pages", "nextpageid", "id", $apageid);
987 } else {
988 return false;
989 }
990 }
991 return false; // should never be reached
992}
993
4b55d2af 994/**
995 * Checks to see if a page is a branch table or is
996 * a page that is enclosed by a branch table and an end of branch or end of lesson.
997 * May call this function: {@link lesson_is_page_in_branch()}
998 *
999 * @param int $lesson_id Id of the lesson to which the page belongs.
1000 * @param int $pageid Id of the page.
1001 * @return boolean True or false.
1002 * @todo Change $lesson_id param to $lessonid.
1003 **/
5e7856af 1004function lesson_display_branch_jumps($lesson_id, $pageid) {
ac8e16be 1005 if($pageid == 0) {
1006 // first page
1007 return false;
1008 }
1009 // get all of the lesson pages
1010 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson_id")) {
1011 // adding first page
1012 return false;
1013 }
1014
1015 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1016 return true;
1017 }
1018
1019 return lesson_is_page_in_branch($lessonpages, $pageid);
5e7856af 1020}
1021
4b55d2af 1022/**
1023 * Checks to see if a page is a cluster page or is
1024 * a page that is enclosed by a cluster page and an end of cluster or end of lesson
1025 * May call this function: {@link lesson_is_page_in_cluster()}
1026 *
1027 * @param int $lesson_id Id of the lesson to which the page belongs.
1028 * @param int $pageid Id of the page.
1029 * @return boolean True or false.
1030 * @todo Change $lesson_id param to $lessonid.
1031 **/
5e7856af 1032function lesson_display_cluster_jump($lesson_id, $pageid) {
ac8e16be 1033 if($pageid == 0) {
1034 // first page
1035 return false;
1036 }
1037 // get all of the lesson pages
1038 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson_id")) {
1039 // adding first page
1040 return false;
1041 }
1042
1043 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1044 return true;
1045 }
1046
1047 return lesson_is_page_in_cluster($lessonpages, $pageid);
5e7856af 1048
1049}
1050
4b55d2af 1051/**
1052 * Checks to see if a LESSON_CLUSTERJUMP or
1053 * a LESSON_UNSEENBRANCHPAGE is used in a lesson.
1054 *
1055 * This function is only executed when a teacher is
1056 * checking the navigation for a lesson.
1057 *
06469639 1058 * @param int $lesson Id of the lesson that is to be checked.
4b55d2af 1059 * @return boolean True or false.
1060 **/
06469639 1061function lesson_display_teacher_warning($lesson) {
ac8e16be 1062 // get all of the lesson answers
1063 if (!$lessonanswers = get_records_select("lesson_answers", "lessonid = $lesson")) {
1064 // no answers, then not useing cluster or unseen
1065 return false;
1066 }
1067 // just check for the first one that fulfills the requirements
1068 foreach ($lessonanswers as $lessonanswer) {
1069 if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) {
1070 return true;
1071 }
1072 }
1073
1074 // if no answers use either of the two jumps
1075 return false;
5e7856af 1076}
1077
1078
4b55d2af 1079/**
1080 * Interprets LESSON_CLUSTERJUMP jumpto value.
1081 *
1082 * This will select a page randomly
1083 * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
1084 * and the page selected will be a page that has not been viewed already
1085 * and if any pages are within a branch table or end of branch then only 1 page within
1086 * the branch table or end of branch will be randomly selected (sub clustering).
1087 *
1088 * @param int $lesson Id of the lesson.
1089 * @param int $user Id of the user.
1090 * @param int $pageid Id of the current page from which we are jumping from.
1091 * @return int The id of the next page.
1092 * @todo Change $lesson param to $lessonid and $user param to $userid
1093 **/
5e7856af 1094function lesson_cluster_jump($lesson, $user, $pageid) {
ac8e16be 1095 // get the number of retakes
5e7856af 1096 if (!$retakes = count_records("lesson_grades", "lessonid", $lesson, "userid", $user)) {
ac8e16be 1097 $retakes = 0;
1098 }
1099
1100 // get all the lesson_attempts aka what the user has seen
1101 if ($seen = get_records_select("lesson_attempts", "lessonid = $lesson AND userid = $user AND retry = $retakes", "timeseen DESC")) {
1102 foreach ($seen as $value) { // load it into an array that I can more easily use
1103 $seenpages[$value->pageid] = $value->pageid;
1104 }
1105 } else {
1106 $seenpages = array();
1107 }
1108
1109 // get the lesson pages
1110 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1111 error("Error: could not find records in lesson_pages table");
1112 }
1113 // find the start of the cluster
1114 while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
1115 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1116 break;
1117 }
1118 $pageid = $lessonpages[$pageid]->prevpageid;
1119 }
1120
1121 $pageid = $lessonpages[$pageid]->nextpageid; // move down from the cluster page
1122
1123 $clusterpages = array();
1124 while (true) { // now load all the pages into the cluster that are not already inside of a branch table.
1125 if ($lessonpages[$pageid]->qtype == LESSON_ENDOFCLUSTER) {
1126 // store the endofcluster page's jump
1127 $exitjump = get_field("lesson_answers", "jumpto", "pageid", $pageid, "lessonid", $lesson);
1128 if ($exitjump == LESSON_NEXTPAGE) {
1129 $exitjump = $lessonpages[$pageid]->nextpageid;
1130 }
1131 if ($exitjump == 0) {
1132 $exitjump = LESSON_EOL;
1133 }
1134 break;
1135 } elseif (!lesson_is_page_in_branch($lessonpages, $pageid) && $lessonpages[$pageid]->qtype != LESSON_ENDOFBRANCH) {
1136 // load page into array when it is not in a branch table and when it is not an endofbranch
1137 $clusterpages[] = $lessonpages[$pageid];
1138 }
1139 if ($lessonpages[$pageid]->nextpageid == 0) {
1140 // shouldn't ever get here... should be using endofcluster
1141 $exitjump = LESSON_EOL;
1142 break;
1143 } else {
1144 $pageid = $lessonpages[$pageid]->nextpageid;
1145 }
1146 }
1147
1148 // filter out the ones we have seen
1149 $unseen = array();
1150 foreach ($clusterpages as $clusterpage) {
1151 if ($clusterpage->qtype == LESSON_BRANCHTABLE) { // if branchtable, check to see if any pages inside have been viewed
1152 $branchpages = lesson_pages_in_branch($lessonpages, $clusterpage->id); // get the pages in the branchtable
1153 $flag = true;
1154 foreach ($branchpages as $branchpage) {
1155 if (array_key_exists($branchpage->id, $seenpages)) { // check if any of the pages have been viewed
1156 $flag = false;
1157 }
1158 }
1159 if ($flag && count($branchpages) > 0) {
1160 // add branch table
1161 $unseen[] = $clusterpage;
1162 }
1163 } else {
1164 // add any other type of page that has not already been viewed
1165 if (!array_key_exists($clusterpage->id, $seenpages)) {
1166 $unseen[] = $clusterpage;
1167 }
1168 }
1169 }
1170
1171 if (count($unseen) > 0) { // it does not contain elements, then use exitjump, otherwise find out next page/branch
1172 $nextpage = $unseen[rand(0, count($unseen)-1)];
1173 } else {
1174 return $exitjump; // seen all there is to see, leave the cluster
1175 }
1176
1177 if ($nextpage->qtype == LESSON_BRANCHTABLE) { // if branch table, then pick a random page inside of it
1178 $branchpages = lesson_pages_in_branch($lessonpages, $nextpage->id);
1179 return $branchpages[rand(0, count($branchpages)-1)]->id;
1180 } else { // otherwise, return the page's id
1181 return $nextpage->id;
1182 }
5e7856af 1183}
1184
4b55d2af 1185/**
1186 * Returns pages that are within a branch table and another branch table, end of branch or end of lesson
1187 *
1188 * @param array $lessonpages An array of lesson page objects.
1189 * @param int $branchid The id of the branch table that we would like the containing pages for.
1190 * @return array An array of lesson page objects.
1191 **/
5e7856af 1192function lesson_pages_in_branch($lessonpages, $branchid) {
ac8e16be 1193 $pageid = $lessonpages[$branchid]->nextpageid; // move to the first page after the branch table
1194 $pagesinbranch = array();
1195
1196 while (true) {
1197 if ($pageid == 0) { // EOL
1198 break;
1199 } elseif ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1200 break;
1201 } elseif ($lessonpages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1202 break;
1203 }
1204 $pagesinbranch[] = $lessonpages[$pageid];
1205 $pageid = $lessonpages[$pageid]->nextpageid;
1206 }
1207
1208 return $pagesinbranch;
5e7856af 1209}
1210
4b55d2af 1211/**
1212 * Interprets the LESSON_UNSEENBRANCHPAGE jump.
1213 *
1214 * will return the pageid of a random unseen page that is within a branch
1215 *
1216 * @see lesson_pages_in_branch()
1217 * @param int $lesson Id of the lesson.
1218 * @param int $user Id of the user.
1219 * @param int $pageid Id of the page from which we are jumping.
1220 * @return int Id of the next page.
1221 * @todo Change params $lesson to $lessonid and $user to $userid.
1222 **/
5e7856af 1223function lesson_unseen_question_jump($lesson, $user, $pageid) {
ac8e16be 1224 // get the number of retakes
5e7856af 1225 if (!$retakes = count_records("lesson_grades", "lessonid", $lesson, "userid", $user)) {
ac8e16be 1226 $retakes = 0;
1227 }
1228
1229 // get all the lesson_attempts aka what the user has seen
1230 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson AND userid = $user AND retry = $retakes", "timeseen DESC")) {
1231 foreach($viewedpages as $viewed) {
1232 $seenpages[] = $viewed->pageid;
1233 }
1234 } else {
1235 $seenpages = array();
1236 }
1237
1238 // get the lesson pages
1239 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1240 error("Error: could not find records in lesson_pages table");
1241 }
1242
1243 if ($pageid == LESSON_UNSEENBRANCHPAGE) { // this only happens when a student leaves in the middle of an unseen question within a branch series
1244 $pageid = $seenpages[0]; // just change the pageid to the last page viewed inside the branch table
1245 }
1246
1247 // go up the pages till branch table
1248 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1249 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1250 break;
1251 }
1252 $pageid = $lessonpages[$pageid]->prevpageid;
1253 }
1254
1255 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1256
1257 // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array
1258 $unseen = array();
1259 foreach($pagesinbranch as $page) {
1260 if (!in_array($page->id, $seenpages)) {
1261 $unseen[] = $page->id;
1262 }
1263 }
1264
1265 if(count($unseen) == 0) {
1266 if(isset($pagesinbranch)) {
1267 $temp = end($pagesinbranch);
1268 $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL
1269 } else {
1270 // there are no pages inside the branch, so return the next page
1271 $nextpage = $lessonpages[$pageid]->nextpageid;
1272 }
1273 if ($nextpage == 0) {
1274 return LESSON_EOL;
1275 } else {
1276 return $nextpage;
1277 }
1278 } else {
1279 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1280 }
5e7856af 1281}
1282
4b55d2af 1283/**
1284 * Handles the unseen branch table jump.
1285 *
1286 * @param int $lesson Lesson id.
1287 * @param int $user User id.
1288 * @return int Will return the page id of a branch table or end of lesson
1289 * @todo Change $lesson param to $lessonid and change $user param to $userid.
1290 **/
5e7856af 1291function lesson_unseen_branch_jump($lesson, $user) {
5e7856af 1292 if (!$retakes = count_records("lesson_grades", "lessonid", $lesson, "userid", $user)) {
ac8e16be 1293 $retakes = 0;
1294 }
1295
1296 if (!$seenbranches = get_records_select("lesson_branch", "lessonid = $lesson AND userid = $user AND retry = $retakes",
1297 "timeseen DESC")) {
1298 error("Error: could not find records in lesson_branch table");
1299 }
1300
1301 // get the lesson pages
1302 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1303 error("Error: could not find records in lesson_pages table");
1304 }
1305
1306 // this loads all the viewed branch tables into $seen untill it finds the branch table with the flag
1307 // which is the branch table that starts the unseenbranch function
1308 $seen = array();
1309 foreach ($seenbranches as $seenbranch) {
1310 if (!$seenbranch->flag) {
1311 $seen[$seenbranch->pageid] = $seenbranch->pageid;
1312 } else {
1313 $start = $seenbranch->pageid;
1314 break;
1315 }
1316 }
1317 // this function searches through the lesson pages to find all the branch tables
1318 // that follow the flagged branch table
1319 $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table
1320 while ($pageid != 0) { // grab all of the branch table till eol
1321 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1322 $branchtables[] = $lessonpages[$pageid]->id;
1323 }
1324 $pageid = $lessonpages[$pageid]->nextpageid;
1325 }
1326 $unseen = array();
1327 foreach ($branchtables as $branchtable) {
1328 // load all of the unseen branch tables into unseen
1329 if (!array_key_exists($branchtable, $seen)) {
1330 $unseen[] = $branchtable;
1331 }
1332 }
1333 if (count($unseen) > 0) {
1334 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1335 } else {
1336 return LESSON_EOL; // has viewed all of the branch tables
1337 }
5e7856af 1338}
1339
4b55d2af 1340/**
1341 * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE).
1342 *
1343 * @param int $lesson Lesson id.
1344 * @param int $pageid The id of the page that we are jumping from (?)
1345 * @return int The pageid of a random page that is within a branch table
1346 * @todo Change $lesson param to $lessonid.
1347 **/
5e7856af 1348function lesson_random_question_jump($lesson, $pageid) {
ac8e16be 1349 // get the lesson pages
1350 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1351 error("Error: could not find records in lesson_pages table");
1352 }
1353
1354 // go up the pages till branch table
1355 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1356
1357 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1358 break;
1359 }
1360 $pageid = $lessonpages[$pageid]->prevpageid;
1361 }
1362
1363 // get the pages within the branch
1364 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1365
1366 if(count($pagesinbranch) == 0) {
1367 // there are no pages inside the branch, so return the next page
1368 return $lessonpages[$pageid]->nextpageid;
1369 } else {
1370 return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id; // returns a random page id for the next page
1371 }
5e7856af 1372}
1373
4b55d2af 1374/**
1375 * Check to see if a page is below a branch table (logically).
1376 *
1377 * Will return true if a branch table is found logically above the page.
1378 * Will return false if an end of branch, cluster or the beginning
1379 * of the lesson is found before a branch table.
1380 *
1381 * @param array $pages An array of lesson page objects.
1382 * @param int $pageid Id of the page for testing.
1383 * @return boolean
1384 */
5e7856af 1385function lesson_is_page_in_branch($pages, $pageid) {
ac8e16be 1386 $pageid = $pages[$pageid]->prevpageid; // move up one
1387
1388 // go up the pages till branch table
1389 while (true) {
1390 if ($pageid == 0) { // ran into the beginning of the lesson
1391 return false;
1392 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFBRANCH) { // ran into the end of another branch table
1393 return false;
1394 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // do not look beyond a cluster
1395 return false;
1396 } elseif ($pages[$pageid]->qtype == LESSON_BRANCHTABLE) { // hit a branch table
1397 return true;
1398 }
1399 $pageid = $pages[$pageid]->prevpageid;
1400 }
5e7856af 1401
1402}
1403
4b55d2af 1404/**
1405 * Check to see if a page is below a cluster page (logically).
1406 *
1407 * Will return true if a cluster is found logically above the page.
1408 * Will return false if an end of cluster or the beginning
1409 * of the lesson is found before a cluster page.
1410 *
1411 * @param array $pages An array of lesson page objects.
1412 * @param int $pageid Id of the page for testing.
1413 * @return boolean
1414 */
5e7856af 1415function lesson_is_page_in_cluster($pages, $pageid) {
ac8e16be 1416 $pageid = $pages[$pageid]->prevpageid; // move up one
1417
1418 // go up the pages till branch table
1419 while (true) {
1420 if ($pageid == 0) { // ran into the beginning of the lesson
1421 return false;
1422 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER) { // ran into the end of another branch table
1423 return false;
1424 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // hit a branch table
1425 return true;
1426 }
1427 $pageid = $pages[$pageid]->prevpageid;
1428 }
5e7856af 1429}
1430
4b55d2af 1431/**
1432 * Prints the contents for the left menu
1433 *
1434 * Runs through all of the lesson pages and calls {@link lesson_print_tree_link_menu()}
1435 * to print out the link.
1436 *
1437 * @see lesson_print_tree_link_menu()
1438 * @param int $lessonid Lesson id.
1439 * @param int $pageid Page id of the first page of the lesson.
1440 * @param int $id The cmid of the lesson.
1441 * @param boolean $showpages An optional paramater to show question pages as well as branch tables in the left menu (NYI)
1442 * @todo change $id param to $cmid. Finnish implementing the $showpages feature.
1443 * Not necessary to pass $pageid, we can find that out in the function.
1444 * Also, no real need for {@link lesson_print_tree_link_menu()}. Everything can be handled in this function.
1445 */
b9869082 1446function lesson_print_tree_menu($lessonid, $pageid, $id, $showpages=false) {
4b55d2af 1447 if (!$pages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
ac8e16be 1448 error("Error: could not find lesson pages");
1449 }
d6941aff 1450 echo '<ul>';
ac8e16be 1451 while ($pageid != 0) {
1452 lesson_print_tree_link_menu($pages[$pageid], $id, true);
1453 $pageid = $pages[$pageid]->nextpageid;
1454 }
d6941aff 1455 echo '</ul>';
5e7856af 1456}
1457
4b55d2af 1458/**
1459 * Prints the actual link for the left menu
1460 *
1461 * Only prints branch tables that have display set to on.
1462 *
1463 * @param object $page Lesson page object.
1464 * @param int $id The cmid of the lesson.
1465 * @param boolean $showpages An optional paramater to show question pages as well as branch tables in the left menu (NYI)
1466 * @todo change $id param to $cmid. Finnish implementing the $showpages feature.
1467 */
b9869082 1468function lesson_print_tree_link_menu($page, $id, $showpages=false) {
ac8e16be 1469 if ($page->qtype == LESSON_BRANCHTABLE && !$page->display) {
1470 return false;
1471 } elseif ($page->qtype != LESSON_BRANCHTABLE) {
1472 return false;
1473 }
1474
1475 /*elseif ($page->qtype != LESSON_BRANCHTABLE && !$showpages) {
1476 return false;
1477 } elseif (!in_array($page->qtype, $LESSON_QUESTION_TYPE)) {
1478 return false;
1479 }*/
1480
1481 // set up some variables NoticeFix changed whole function
1482 $output = "";
d6941aff 1483 $class = ' class="leftmenu_not_selected_link" ';
ac8e16be 1484
97d2756c 1485 if($page->id == optional_param('pageid', 0, PARAM_INT)) {
1486 $class = ' class="leftmenu_selected_link" ';
1487 }
ac8e16be 1488
d6941aff 1489 $output .= '<li>';
ac8e16be 1490
c94d3799 1491 $output .= "<a $class href=\"view.php?id=$id&amp;pageid=$page->id\">".format_string($page->title,true)."</a>\n";
d6941aff 1492
1493 $output .= "</li>";
5e7856af 1494
d6941aff 1495 echo $output;
5e7856af 1496}
1497
4b55d2af 1498/**
1499 * Calculates a user's grade for a lesson.
1500 *
4b55d2af 1501 * @param object $lesson The lesson that the user is taking.
4b55d2af 1502 * @param int $retries The attempt number.
88427c07 1503 * @param int $userid Id of the user (optinal, default current user).
1504 * @return object { nquestions => number of questions answered
1505 attempts => number of question attempts
1506 total => max points possible
1507 earned => points earned by student
1508 grade => calculated percentage grade
1509 nmanual => number of manually graded questions
1510 manualpoints => point value for manually graded questions }
4b55d2af 1511 */
88427c07 1512function lesson_grade($lesson, $ntries, $userid = 0) {
1513 global $USER;
ac8e16be 1514
88427c07 1515 if (empty($userid)) {
1516 $userid = $USER->id;
1517 }
1518
1519 // Zero out everything
1520 $ncorrect = 0;
1521 $nviewed = 0;
1522 $score = 0;
1523 $nmanual = 0;
1524 $manualpoints = 0;
1525 $thegrade = 0;
1526 $nquestions = 0;
1527 $total = 0;
1528 $earned = 0;
1529
1530 if ($useranswers = get_records_select("lesson_attempts", "lessonid = $lesson->id AND
1531 userid = $userid AND retry = $ntries", "timeseen")) {
1532 // group each try with its page
1533 $attemptset = array();
1534 foreach ($useranswers as $useranswer) {
1535 $attemptset[$useranswer->pageid][] = $useranswer;
ac8e16be 1536 }
88427c07 1537
1538 // Drop all attempts that go beyond max attempts for the lesson
1539 foreach ($attemptset as $key => $set) {
1540 $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts);
1541 }
1542
1543 $pageids = implode(",", array_keys($attemptset));
1544
1545 // get only the pages and their answers that the user answered
1546 $pages = get_records_select("lesson_pages", "lessonid = $lesson->id AND id IN($pageids)");
1547 $answers = get_records_select("lesson_answers", "lessonid = $lesson->id AND pageid IN($pageids)");
1548
1549 // Number of pages answered
1550 $nquestions = count($pages);
1551
1552 foreach ($attemptset as $attempts) {
1553 if ($lesson->custom) {
1554 $attempt = end($attempts);
1555 // If essay question, handle it, otherwise add to score
1556 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
ac8e16be 1557 $essayinfo = unserialize($attempt->useranswer);
88427c07 1558 $earned += $essayinfo->score;
1559 $nmanual++;
1560 $manualpoints += $answers[$attempt->answerid]->score;
ac8e16be 1561 } else {
88427c07 1562 $earned += $answers[$attempt->answerid]->score;
1563 }
1564 } else {
1565 foreach ($attempts as $attempt) {
1566 $earned += $attempt->correct;
1567 }
1568 $attempt = end($attempts); // doesn't matter which one
1569 // If essay question, increase numbers
1570 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1571 $nmanual++;
1572 $manualpoints++;
ac8e16be 1573 }
1574 }
88427c07 1575 // Number of times answered
1576 $nviewed += count($attempts);
1577 }
1578
1579 if ($lesson->custom) {
ac8e16be 1580 $bestscores = array();
88427c07 1581 // Find the highest possible score per page to get our total
1582 foreach ($answers as $answer) {
46341ab7 1583 if(!isset($bestscores[$answer->pageid])) {
88427c07 1584 $bestscores[$answer->pageid] = $answer->score;
46341ab7 1585 } else if ($bestscores[$answer->pageid] < $answer->score) {
88427c07 1586 $bestscores[$answer->pageid] = $answer->score;
ac8e16be 1587 }
1588 }
88427c07 1589 $total = array_sum($bestscores);
1590 } else {
1591 // Check to make sure the student has answered the minimum questions
1592 if ($lesson->minquestions and $nquestions < $lesson->minquestions) {
1593 // Nope, increase number viewed by the amount of unanswered questions
1594 $total = $nviewed + ($lesson->minquestions - $nquestions);
1595 } else {
1596 $total = $nviewed;
1597 }
ac8e16be 1598 }
88427c07 1599 }
1600
1601 if ($total) { // not zero
1602 $thegrade = round(100 * $earned / $total, 5);
1603 }
1604
1605 // Build the grade information object
1606 $gradeinfo = new stdClass;
1607 $gradeinfo->nquestions = $nquestions;
1608 $gradeinfo->attempts = $nviewed;
1609 $gradeinfo->total = $total;
1610 $gradeinfo->earned = $earned;
1611 $gradeinfo->grade = $thegrade;
1612 $gradeinfo->nmanual = $nmanual;
1613 $gradeinfo->manualpoints = $manualpoints;
1614
1615 return $gradeinfo;
1616}
1617
1618/**
1619 * Prints the on going message to the user.
1620 *
1621 * With custom grading On, displays points
1622 * earned out of total points possible thus far.
1623 * With custom grading Off, displays number of correct
1624 * answers out of total attempted.
1625 *
1626 * @param object $lesson The lesson that the user is taking.
1627 * @return void
1628 **/
1629function lesson_print_ongoing_score($lesson) {
1630 global $USER;
6e1ff5c8 1631 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1632 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1633
1634 if (has_capability('mod/lesson:manage', $context)) {
88427c07 1635 echo "<p align=\"center\">".get_string('teacherongoingwarning', 'lesson').'</p>';
1636 } else {
1637 $ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id);
1638 if (isset($USER->modattempts[$lesson->id])) {
1639 $ntries--;
1640 }
1641 $gradeinfo = lesson_grade($lesson, $ntries);
ac8e16be 1642
88427c07 1643 $a = new stdClass;
1644 if ($lesson->custom) {
1645 $a->score = $gradeinfo->earned;
1646 $a->currenthigh = $gradeinfo->total;
1647 print_simple_box(get_string("ongoingcustom", "lesson", $a), "center");
ac8e16be 1648 } else {
88427c07 1649 $a->correct = $gradeinfo->earned;
1650 $a->viewed = $gradeinfo->attempts;
1651 print_simple_box(get_string("ongoingnormal", "lesson", $a), "center");
ac8e16be 1652 }
1653 }
5e7856af 1654}
1655
4b55d2af 1656/**
1657 * Prints tabs for the editing and adding pages. Each tab is a question type.
1658 *
1659 * @param array $qtypes The question types array (may not need to pass this because it is defined in this file)
1660 * @param string $selected Current selected tab
1661 * @param string $link The base href value of the link for the tab
1662 * @param string $onclick Javascript for the tab link
1663 * @return void
1664 */
5e7856af 1665function lesson_qtype_menu($qtypes, $selected="", $link="", $onclick="") {
271fea97 1666 $tabs = array();
1667 $tabrows = array();
1668
9638a1f4 1669 foreach ($qtypes as $qtype => $qtypename) {
7ac95a71 1670 $tabrows[] = new tabobject($qtype, "$link&amp;qtype=$qtype\" onClick=\"$onclick", $qtypename);
ac8e16be 1671 }
271fea97 1672 $tabs[] = $tabrows;
1673 print_tabs($tabs, $selected);
1674 echo "<input type=\"hidden\" name=\"qtype\" value=\"$selected\" /> \n";
9638a1f4 1675
5e7856af 1676}
1677
4b55d2af 1678/**
1679 * Checks to see if the nickname is naughty or not.
1680 *
1681 * @todo Move this to highscores.php
1682 */
5e7856af 1683function lesson_check_nickname($name) {
5e7856af 1684
832b9f82 1685 if (empty($name)) {
ac8e16be 1686 return false;
1687 }
1688
832b9f82 1689 $filterwords = explode(',', get_string('censorbadwords'));
ac8e16be 1690
1691 foreach ($filterwords as $filterword) {
1692 if (strstr($name, $filterword)) {
1693 return false;
1694 }
1695 }
1696 return true;
5e7856af 1697}
57bfe93d 1698
62eda6ea 1699/**
1700 * Prints out a Progress Bar which depicts a user's progress within a lesson.
1701 *
1702 * Currently works best with a linear lesson. Clusters are counted as a single page.
1703 * Also, only viewed branch tables and questions that have been answered correctly count
1704 * toward lesson completion (or progress). Only Students can see the Progress bar as well.
1705 *
1706 * @param object $lesson The lesson that the user is currently taking.
1707 * @param object $course The course that the to which the lesson belongs.
1708 * @return boolean The return is not significant as of yet. Will return true/false.
62eda6ea 1709 **/
1710function lesson_print_progress_bar($lesson, $course) {
1711 global $CFG, $USER;
6e1ff5c8 1712 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1713 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1714
62eda6ea 1715 // lesson setting to turn progress bar on or off
1716 if (!$lesson->progressbar) {
1717 return false;
1718 }
1719
1720 // catch teachers
6e1ff5c8 1721 if (has_capability('mod/lesson:manage', $context)) {
62eda6ea 1722 notify(get_string('progressbarteacherwarning', 'lesson', $course->teachers));
1723 return false;
1724 }
acab9099 1725 if (!isset($USER->modattempts[$lesson->id])) {
1726 // all of the lesson pages
1727 if (!$pages = get_records('lesson_pages', 'lessonid', $lesson->id)) {
1728 return false;
1729 } else {
1730 foreach ($pages as $page) {
1731 if ($page->prevpageid == 0) {
1732 $pageid = $page->id; // find the first page id
1733 break;
1734 }
62eda6ea 1735 }
1736 }
62eda6ea 1737
acab9099 1738 // current attempt number
1739 if (!$ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id)) {
1740 $ntries = 0; // may not be necessary
1741 }
62eda6ea 1742
acab9099 1743 $viewedpageids = array();
62eda6ea 1744
acab9099 1745 // collect all of the correctly answered questions
1746 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries AND correct = 1", 'timeseen DESC', 'pageid, id')) {
1747 $viewedpageids = array_keys($viewedpages);
1748 }
1749 // collect all of the branch tables viewed
1750 if ($viewedbranches = get_records_select("lesson_branch", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries", 'timeseen DESC', 'pageid, id')) {
1751 $viewedpageids = array_merge($viewedpageids, array_keys($viewedbranches));
1752 }
1753
1754 // Filter out the following pages:
1755 // End of Cluster
1756 // End of Branch
1757 // Pages found inside of Clusters
1758 // Do not filter out Cluster Page(s) because we count a cluster as one.
1759 // By keeping the cluster page, we get our 1
1760 $validpages = array();
1761 while ($pageid != 0) {
1762 if ($pages[$pageid]->qtype == LESSON_CLUSTER) {
1763 $clusterpageid = $pageid; // copy it
1764 $validpages[$clusterpageid] = 1; // add the cluster page as a valid page
1765 $pageid = $pages[$pageid]->nextpageid; // get next page
62eda6ea 1766
acab9099 1767 // now, remove all necessary viewed paged ids from the viewedpageids array.
1768 while ($pages[$pageid]->qtype != LESSON_ENDOFCLUSTER and $pageid != 0) {
1769 if (in_array($pageid, $viewedpageids)) {
1770 unset($viewedpageids[array_search($pageid, $viewedpageids)]); // remove it
1771 // since the user did see one page in the cluster, add the cluster pageid to the viewedpageids
1772 if (!in_array($clusterpageid, $viewedpageids)) {
1773 $viewedpageids[] = $clusterpageid;
1774 }
62eda6ea 1775 }
acab9099 1776 $pageid = $pages[$pageid]->nextpageid;
62eda6ea 1777 }
acab9099 1778 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER or $pages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1779 // dont count these, just go to next
1780 $pageid = $pages[$pageid]->nextpageid;
1781 } else {
1782 // a counted page
1783 $validpages[$pageid] = 1;
62eda6ea 1784 $pageid = $pages[$pageid]->nextpageid;
1785 }
acab9099 1786 }
62eda6ea 1787
acab9099 1788 // progress calculation as a percent
1789 $progress = round(count($viewedpageids)/count($validpages), 2) * 100;
1790 } else {
1791 $progress = 100;
1792 }
62eda6ea 1793
1794 // print out the Progress Bar. Attempted to put as much as possible in the style sheets.
1795 echo '<div class="progress_bar" align="center">';
1796 echo '<table class="progress_bar_table"><tr>';
1797 if ($progress != 0) { // some browsers do not repsect the 0 width.
1798 echo '<td width="'.$progress.'%" class="progress_bar_completed">';
1799 echo '</td>';
1800 }
1801 echo '<td class="progress_bar_todo">';
1802 echo '<div class="progress_bar_token"></div>';
1803 echo '</td>';
1804 echo '</tr></table>';
1805 echo '</div>';
1806
1807 return true;
1808}
1809
1810/**
1811 * Determines if a user can view the left menu. The determining factor
1812 * is whether a user has a grade greater than or equal to the lesson setting
1813 * of displayleftif
1814 *
1815 * @param object $lesson Lesson object of the current lesson
1816 * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged
62eda6ea 1817 **/
1818function lesson_displayleftif($lesson) {
1819 global $CFG, $USER;
1820
1821 if (!empty($lesson->displayleftif)) {
1822 // get the current user's max grade for this lesson
1823 if ($maxgrade = get_record_sql('SELECT userid, MAX(grade) AS maxgrade FROM '.$CFG->prefix.'lesson_grades WHERE userid = '.$USER->id.' AND lessonid = '.$lesson->id.' GROUP BY userid')) {
1824 if ($maxgrade->maxgrade < $lesson->displayleftif) {
1825 return 0; // turn off the displayleft
1826 }
1827 } else {
1828 return 0; // no grades
1829 }
1830 }
1831
1832 // if we get to here, keep the original state of displayleft lesson setting
1833 return $lesson->displayleft;
1834}
5e7856af 1835
2a488ba5 1836?>