MDL-11082 Improved groups upgrade performance 1.8x -> 1.9; thanks Eloy for telling...
[moodle-pu.git] / mod / lesson / locallib.php
blob0c8044bd42ea00c0934808b77d22d57e30fd2ea2
1 <?php // $Id$
2 /**
3 * Local library file for Lesson. These are non-standard functions that are used
4 * only by Lesson.
6 * @version $Id$
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package lesson
9 **/
11 /**
12 * Next page -> any page not seen before
13 */
14 if (!defined("LESSON_UNSEENPAGE")) {
15 define("LESSON_UNSEENPAGE", 1); // Next page -> any page not seen before
17 /**
18 * Next page -> any page not answered correctly
20 if (!defined("LESSON_UNANSWEREDPAGE")) {
21 define("LESSON_UNANSWEREDPAGE", 2); // Next page -> any page not answered correctly
24 /**
25 * Define different lesson flows for next page
27 $LESSON_NEXTPAGE_ACTION = array (0 => get_string("normal", "lesson"),
28 LESSON_UNSEENPAGE => get_string("showanunseenpage", "lesson"),
29 LESSON_UNANSWEREDPAGE => get_string("showanunansweredpage", "lesson") );
31 // Lesson jump types defined
32 // TODO: instead of using define statements, create an array with all the jump values
34 /**
35 * Jump to Next Page
37 if (!defined("LESSON_NEXTPAGE")) {
38 define("LESSON_NEXTPAGE", -1);
40 /**
41 * End of Lesson
43 if (!defined("LESSON_EOL")) {
44 define("LESSON_EOL", -9);
46 /**
47 * Jump to an unseen page within a branch and end of branch or end of lesson
49 if (!defined("LESSON_UNSEENBRANCHPAGE")) {
50 define("LESSON_UNSEENBRANCHPAGE", -50);
52 /**
53 * Jump to Previous Page
55 if (!defined("LESSON_PREVIOUSPAGE")) {
56 define("LESSON_PREVIOUSPAGE", -40);
58 /**
59 * Jump to a random page within a branch and end of branch or end of lesson
61 if (!defined("LESSON_RANDOMPAGE")) {
62 define("LESSON_RANDOMPAGE", -60);
64 /**
65 * Jump to a random Branch
67 if (!defined("LESSON_RANDOMBRANCH")) {
68 define("LESSON_RANDOMBRANCH", -70);
70 /**
71 * Cluster Jump
73 if (!defined("LESSON_CLUSTERJUMP")) {
74 define("LESSON_CLUSTERJUMP", -80);
76 /**
77 * Undefined
78 */
79 if (!defined("LESSON_UNDEFINED")) {
80 define("LESSON_UNDEFINED", -99);
83 // Lesson question types defined
85 /**
86 * Short answer question type
88 if (!defined("LESSON_SHORTANSWER")) {
89 define("LESSON_SHORTANSWER", "1");
91 /**
92 * True/False question type
94 if (!defined("LESSON_TRUEFALSE")) {
95 define("LESSON_TRUEFALSE", "2");
97 /**
98 * Multichoice question type
100 * If you change the value of this then you need
101 * to change it in restorelib.php as well.
103 if (!defined("LESSON_MULTICHOICE")) {
104 define("LESSON_MULTICHOICE", "3");
107 * Random question type - not used
109 if (!defined("LESSON_RANDOM")) {
110 define("LESSON_RANDOM", "4");
113 * Matching question type
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.
119 if (!defined("LESSON_MATCHING")) {
120 define("LESSON_MATCHING", "5");
123 * Not sure - not used
125 if (!defined("LESSON_RANDOMSAMATCH")) {
126 define("LESSON_RANDOMSAMATCH", "6");
129 * Not sure - not used
131 if (!defined("LESSON_DESCRIPTION")) {
132 define("LESSON_DESCRIPTION", "7");
135 * Numerical question type
137 if (!defined("LESSON_NUMERICAL")) {
138 define("LESSON_NUMERICAL", "8");
141 * Multichoice with multianswer question type
143 if (!defined("LESSON_MULTIANSWER")) {
144 define("LESSON_MULTIANSWER", "9");
147 * Essay question type
149 if (!defined("LESSON_ESSAY")) {
150 define("LESSON_ESSAY", "10");
154 * Lesson question type array.
155 * Contains all question types used
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"),
162 LESSON_ESSAY => get_string("essay", "lesson")
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"),
169 // Non-question page types
172 * Branch Table page
174 if (!defined("LESSON_BRANCHTABLE")) {
175 define("LESSON_BRANCHTABLE", "20");
178 * End of Branch page
180 if (!defined("LESSON_ENDOFBRANCH")) {
181 define("LESSON_ENDOFBRANCH", "21");
184 * Start of Cluster page
186 if (!defined("LESSON_CLUSTER")) {
187 define("LESSON_CLUSTER", "30");
190 * End of Cluster page
192 if (!defined("LESSON_ENDOFCLUSTER")) {
193 define("LESSON_ENDOFCLUSTER", "31");
196 // other variables...
199 * Flag for the editor for the answer textarea.
201 if (!defined("LESSON_ANSWER_EDITOR")) {
202 define("LESSON_ANSWER_EDITOR", "1");
205 * Flag for the editor for the response textarea.
207 if (!defined("LESSON_RESPONSE_EDITOR")) {
208 define("LESSON_RESPONSE_EDITOR", "2");
211 //////////////////////////////////////////////////////////////////////////////////////
212 /// Any other lesson functions go here. Each of them must have a name that
213 /// starts with lesson_
216 * Print the standard header for lesson module
218 * @uses $CFG
219 * @uses $USER
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
224 * @return boolean
226 function lesson_print_header($cm, $course, $lesson, $currenttab = '') {
227 global $CFG, $USER;
229 $strlessons = get_string('modulenameplural', 'lesson');
230 $strlesson = get_string('modulename', 'lesson');
231 $strname = format_string($lesson->name, true);
233 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
235 if (has_capability('mod/lesson:edit', $context)) {
236 $button = update_module_button($cm->id, $course->id, $strlesson);
237 } else {
238 $button = '';
241 /// Header setup
242 $navlinks = array();
243 $navlinks[] = array('name' => $strlessons, 'link' => "$CFG->wwwroot/mod/lesson/index.php?id=$course->id", 'type' => 'activity');
244 $navlinks[] = array('name' => $strname, 'link' => '', 'type' => 'activityinstance');
246 $navigation = build_navigation($navlinks);
248 /// Print header, heading, tabs and messages
249 print_header("$course->shortname: $strname", $course->fullname, $navigation,
250 '', '', true, $button, navmenu($course, $cm));
252 if (has_capability('mod/lesson:manage', $context)) {
253 print_heading_with_help(format_string($lesson->name, true), "overview", "lesson");
255 if (!empty($currenttab)) {
256 include($CFG->dirroot.'/mod/lesson/tabs.php');
258 } else {
259 print_heading(format_string($lesson->name, true));
262 lesson_print_messages();
264 return true;
268 * Returns course module, course and module instance given
269 * either the course module ID or a lesson module ID.
271 * @param int $cmid Course Module ID
272 * @param int $lessonid Lesson module instance ID
273 * @return array array($cm, $course, $lesson)
275 function lesson_get_basics($cmid = 0, $lessonid = 0) {
276 if ($cmid) {
277 if (!$cm = get_coursemodule_from_id('lesson', $cmid)) {
278 error('Course Module ID was incorrect');
280 if (!$course = get_record('course', 'id', $cm->course)) {
281 error('Course is misconfigured');
283 if (!$lesson = get_record('lesson', 'id', $cm->instance)) {
284 error('Course module is incorrect');
286 } else if ($lessonid) {
287 if (!$lesson = get_record('lesson', 'id', $lessonid)) {
288 error('Course module is incorrect');
290 if (!$course = get_record('course', 'id', $lesson->course)) {
291 error('Course is misconfigured');
293 if (!$cm = get_coursemodule_from_instance('lesson', $lesson->id, $course->id)) {
294 error('Course Module ID was incorrect');
296 } else {
297 error('No course module ID or lesson ID were passed');
300 return array($cm, $course, $lesson);
304 * Sets a message to be printed. Messages are printed
305 * by calling {@link lesson_print_messages()}.
307 * @uses $SESSION
308 * @param string $message The message to be printed
309 * @param string $class Class to be passed to {@link notify()}. Usually notifyproblem or notifysuccess.
310 * @param string $align Alignment of the message
311 * @return boolean
313 function lesson_set_message($message, $class="notifyproblem", $align='center') {
314 global $SESSION;
316 if (empty($SESSION->lesson_messages) or !is_array($SESSION->lesson_messages)) {
317 $SESSION->lesson_messages = array();
320 $SESSION->lesson_messages[] = array($message, $class, $align);
322 return true;
326 * Print all set messages.
328 * See {@link lesson_set_message()} for setting messages.
330 * Uses {@link notify()} to print the messages.
332 * @uses $SESSION
333 * @return boolean
335 function lesson_print_messages() {
336 global $SESSION;
338 if (empty($SESSION->lesson_messages)) {
339 // No messages to print
340 return true;
343 foreach($SESSION->lesson_messages as $message) {
344 notify($message[0], $message[1], $message[2]);
347 // Reset
348 unset($SESSION->lesson_messages);
350 return true;
354 * Prints a lesson link that submits a form.
356 * If Javascript is disabled, then a regular submit button is printed
358 * @param string $name Name of the link or button
359 * @param string $form The name of the form to be submitted
360 * @param string $align Alignment of the button
361 * @param string $class Class names to add to the div wrapper
362 * @param string $title Title for the link (Not used if javascript is disabled)
363 * @param string $id ID tag
364 * @param boolean $return Return flag
365 * @return mixed boolean/html
367 function lesson_print_submit_link($name, $form, $align = 'center', $class='standardbutton', $title = '', $id = '', $return = false) {
368 if (!empty($align)) {
369 $align = " style=\"text-align:$align\"";
371 if (!empty($id)) {
372 $id = " id=\"$id\"";
374 if (empty($title)) {
375 $title = $name;
378 $output = "<div class=\"lessonbutton $class\" $align>\n";
379 $output .= "<input type=\"submit\" value=\"$name\" $align $id />";
380 $output .= "</div>\n";
382 if ($return) {
383 return $output;
384 } else {
385 echo $output;
386 return true;
391 * Prints a time remaining in the following format: H:MM:SS
393 * @param int $starttime Time when the lesson started
394 * @param int $maxtime Length of the lesson
395 * @param boolean $return Return output switch
396 * @return mixed boolean/string
398 function lesson_print_time_remaining($starttime, $maxtime, $return = false) {
399 // Calculate hours, minutes and seconds
400 $timeleft = $starttime + $maxtime * 60 - time();
401 $hours = floor($timeleft/3600);
402 $timeleft = $timeleft - ($hours * 3600);
403 $minutes = floor($timeleft/60);
404 $secs = $timeleft - ($minutes * 60);
406 if ($minutes < 10) {
407 $minutes = "0$minutes";
409 if ($secs < 10) {
410 $secs = "0$secs";
412 $output = array();
413 $output[] = $hours;
414 $output[] = $minutes;
415 $output[] = $secs;
417 $output = implode(':', $output);
419 if ($return) {
420 return $output;
421 } else {
422 echo $output;
423 return true;
428 * Prints the page action buttons
430 * Move/Edit/Preview/Delete
432 * @uses $CFG
433 * @param int $cmid Course Module ID
434 * @param object $page Page record
435 * @param boolean $printmove Flag to print the move button or not
436 * @param boolean $printaddpage Flag to print the add page drop-down or not
437 * @param boolean $return Return flag
438 * @return mixed boolean/string
440 function lesson_print_page_actions($cmid, $page, $printmove, $printaddpage = false, $return = false) {
441 global $CFG;
443 $context = get_context_instance(CONTEXT_MODULE, $cmid);
444 $actions = array();
446 if (has_capability('mod/lesson:edit', $context)) {
447 if ($printmove) {
448 $actions[] = "<a title=\"".get_string('move')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=move&amp;pageid=$page->id\">
449 <img src=\"$CFG->pixpath/t/move.gif\" class=\"iconsmall\" alt=\"".get_string('move')."\" /></a>\n";
451 $actions[] = "<a title=\"".get_string('update')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=editpage&amp;pageid=$page->id\">
452 <img src=\"$CFG->pixpath/t/edit.gif\" class=\"iconsmall\" alt=\"".get_string('update')."\" /></a>\n";
454 $actions[] = "<a title=\"".get_string('preview')."\" href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&amp;pageid=$page->id\">
455 <img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"".get_string('preview')."\" /></a>\n";
457 $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\">
458 <img src=\"$CFG->pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"".get_string('delete')."\" /></a>\n";
460 if ($printaddpage) {
461 // Add page drop-down
462 $options = array();
463 $options['addcluster&amp;sesskey='.sesskey()] = get_string('clustertitle', 'lesson');
464 $options['addendofcluster&amp;sesskey='.sesskey()] = get_string('endofclustertitle', 'lesson');
465 $options['addbranchtable'] = get_string('branchtable', 'lesson');
466 $options['addendofbranch&amp;sesskey='.sesskey()] = get_string('endofbranch', 'lesson');
467 $options['addpage'] = get_string('question', 'lesson');
468 // Base url
469 $common = "$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;pageid=$page->id&amp;action=";
471 $actions[] = popup_form($common, $options, "addpage_$page->id", '', get_string('addpage', 'lesson').'...', '', '', true);
475 $actions = implode(' ', $actions);
477 if ($return) {
478 return $actions;
479 } else {
480 echo $actions;
481 return false;
486 * Prints the add links in expanded view or single view when editing
488 * @uses $CFG
489 * @param int $cmid Course Module ID
490 * @param int $prevpageid Previous page id
491 * @param boolean $return Return flag
492 * @return mixed boolean/string
493 * @todo &amp;pageid does not make sense, it is prevpageid
495 function lesson_print_add_links($cmid, $prevpageid, $return = false) {
496 global $CFG;
498 $context = get_context_instance(CONTEXT_MODULE, $cmid);
500 $links = '';
501 if (has_capability('mod/lesson:edit', $context)) {
502 $links = array();
503 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/import.php?id=$cmid&amp;pageid=$prevpageid\">".
504 get_string('importquestions', 'lesson').'</a>';
506 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addcluster&amp;pageid=$prevpageid\">".
507 get_string('addcluster', 'lesson').'</a>';
509 if ($prevpageid != 0) {
510 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofcluster&amp;pageid=$prevpageid\">".
511 get_string('addendofcluster', 'lesson').'</a>';
513 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addbranchtable&amp;pageid=$prevpageid\">".
514 get_string('addabranchtable', 'lesson').'</a>';
516 if ($prevpageid != 0) {
517 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofbranch&amp;pageid=$prevpageid\">".
518 get_string('addanendofbranch', 'lesson').'</a>';
521 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addpage&amp;pageid=$prevpageid\">".
522 get_string('addaquestionpagehere', 'lesson').'</a>';
524 $links = implode(" | \n", $links);
525 $links = "\n<div class=\"addlinks\">\n$links\n</div>\n";
528 if ($return) {
529 return $links;
530 } else {
531 echo $links;
532 return true;
537 * Returns the string for a page type
539 * @uses $LESSON_QUESTION_TYPE
540 * @param int $qtype Page type
541 * @return string
543 function lesson_get_qtype_name($qtype) {
544 global $LESSON_QUESTION_TYPE;
545 switch ($qtype) {
546 case LESSON_ESSAY :
547 case LESSON_SHORTANSWER :
548 case LESSON_MULTICHOICE :
549 case LESSON_MATCHING :
550 case LESSON_TRUEFALSE :
551 case LESSON_NUMERICAL :
552 return $LESSON_QUESTION_TYPE[$qtype];
553 break;
554 case LESSON_BRANCHTABLE :
555 return get_string("branchtable", "lesson");
556 break;
557 case LESSON_ENDOFBRANCH :
558 return get_string("endofbranch", "lesson");
559 break;
560 case LESSON_CLUSTER :
561 return get_string("clustertitle", "lesson");
562 break;
563 case LESSON_ENDOFCLUSTER :
564 return get_string("endofclustertitle", "lesson");
565 break;
566 default:
567 return '';
568 break;
573 * Returns the string for a jump name
575 * @param int $jumpto Jump code or page ID
576 * @return string
578 function lesson_get_jump_name($jumpto) {
579 if ($jumpto == 0) {
580 $jumptitle = get_string('thispage', 'lesson');
581 } elseif ($jumpto == LESSON_NEXTPAGE) {
582 $jumptitle = get_string('nextpage', 'lesson');
583 } elseif ($jumpto == LESSON_EOL) {
584 $jumptitle = get_string('endoflesson', 'lesson');
585 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
586 $jumptitle = get_string('unseenpageinbranch', 'lesson');
587 } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
588 $jumptitle = get_string('previouspage', 'lesson');
589 } elseif ($jumpto == LESSON_RANDOMPAGE) {
590 $jumptitle = get_string('randompageinbranch', 'lesson');
591 } elseif ($jumpto == LESSON_RANDOMBRANCH) {
592 $jumptitle = get_string('randombranch', 'lesson');
593 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
594 $jumptitle = get_string('clusterjump', 'lesson');
595 } else {
596 if (!$jumptitle = get_field('lesson_pages', 'title', 'id', $jumpto)) {
597 $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
601 return format_string($jumptitle,true);
605 * Given some question info and some data about the the answers
606 * this function parses, organises and saves the question
608 * This is only used when IMPORTING questions and is only called
609 * from format.php
610 * Lifted from mod/quiz/lib.php -
611 * 1. all reference to oldanswers removed
612 * 2. all reference to quiz_multichoice table removed
613 * 3. In SHORTANSWER questions usecase is store in the qoption field
614 * 4. In NUMERIC questions store the range as two answers
615 * 5. TRUEFALSE options are ignored
616 * 6. For MULTICHOICE questions with more than one answer the qoption field is true
618 * @param opject $question Contains question data like question, type and answers.
619 * @return object Returns $result->error or $result->notice.
621 function lesson_save_question_options($question) {
623 $timenow = time();
624 switch ($question->qtype) {
625 case LESSON_SHORTANSWER:
627 $answers = array();
628 $maxfraction = -1;
630 // Insert all the new answers
631 foreach ($question->answer as $key => $dataanswer) {
632 if ($dataanswer != "") {
633 $answer = new stdClass;
634 $answer->lessonid = $question->lessonid;
635 $answer->pageid = $question->id;
636 if ($question->fraction[$key] >=0.5) {
637 $answer->jumpto = LESSON_NEXTPAGE;
639 $answer->timecreated = $timenow;
640 $answer->grade = $question->fraction[$key] * 100;
641 $answer->answer = $dataanswer;
642 $answer->response = $question->feedback[$key];
643 if (!$answer->id = insert_record("lesson_answers", $answer)) {
644 $result->error = "Could not insert shortanswer quiz answer!";
645 return $result;
647 $answers[] = $answer->id;
648 if ($question->fraction[$key] > $maxfraction) {
649 $maxfraction = $question->fraction[$key];
655 /// Perform sanity checks on fractional grades
656 if ($maxfraction != 1) {
657 $maxfraction = $maxfraction * 100;
658 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
659 return $result;
661 break;
663 case LESSON_NUMERICAL: // Note similarities to SHORTANSWER
665 $answers = array();
666 $maxfraction = -1;
669 // for each answer store the pair of min and max values even if they are the same
670 foreach ($question->answer as $key => $dataanswer) {
671 if ($dataanswer != "") {
672 $answer = new stdClass;
673 $answer->lessonid = $question->lessonid;
674 $answer->pageid = $question->id;
675 $answer->jumpto = LESSON_NEXTPAGE;
676 $answer->timecreated = $timenow;
677 $answer->grade = $question->fraction[$key] * 100;
678 $min = $question->answer[$key] - $question->tolerance[$key];
679 $max = $question->answer[$key] + $question->tolerance[$key];
680 $answer->answer = $min.":".$max;
681 // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max
682 $answer->response = $question->feedback[$key];
683 if (!$answer->id = insert_record("lesson_answers", $answer)) {
684 $result->error = "Could not insert numerical quiz answer!";
685 return $result;
688 $answers[] = $answer->id;
689 if ($question->fraction[$key] > $maxfraction) {
690 $maxfraction = $question->fraction[$key];
695 /// Perform sanity checks on fractional grades
696 if ($maxfraction != 1) {
697 $maxfraction = $maxfraction * 100;
698 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
699 return $result;
701 break;
704 case LESSON_TRUEFALSE:
706 // the truth
707 $answer->lessonid = $question->lessonid;
708 $answer->pageid = $question->id;
709 $answer->timecreated = $timenow;
710 $answer->answer = get_string("true", "quiz");
711 $answer->grade = $question->answer * 100;
712 if ($answer->grade > 50 ) {
713 $answer->jumpto = LESSON_NEXTPAGE;
715 if (isset($question->feedbacktrue)) {
716 $answer->response = $question->feedbacktrue;
718 if (!$true->id = insert_record("lesson_answers", $answer)) {
719 $result->error = "Could not insert quiz answer \"true\")!";
720 return $result;
723 // the lie
724 $answer = new stdClass;
725 $answer->lessonid = $question->lessonid;
726 $answer->pageid = $question->id;
727 $answer->timecreated = $timenow;
728 $answer->answer = get_string("false", "quiz");
729 $answer->grade = (1 - (int)$question->answer) * 100;
730 if ($answer->grade > 50 ) {
731 $answer->jumpto = LESSON_NEXTPAGE;
733 if (isset($question->feedbackfalse)) {
734 $answer->response = $question->feedbackfalse;
736 if (!$false->id = insert_record("lesson_answers", $answer)) {
737 $result->error = "Could not insert quiz answer \"false\")!";
738 return $result;
741 break;
744 case LESSON_MULTICHOICE:
746 $totalfraction = 0;
747 $maxfraction = -1;
749 $answers = array();
751 // Insert all the new answers
752 foreach ($question->answer as $key => $dataanswer) {
753 if ($dataanswer != "") {
754 $answer = new stdClass;
755 $answer->lessonid = $question->lessonid;
756 $answer->pageid = $question->id;
757 $answer->timecreated = $timenow;
758 $answer->grade = $question->fraction[$key] * 100;
759 // changed some defaults
760 /* Original Code
761 if ($answer->grade > 50 ) {
762 $answer->jumpto = LESSON_NEXTPAGE;
764 Replaced with: */
765 if ($answer->grade > 50 ) {
766 $answer->jumpto = LESSON_NEXTPAGE;
767 $answer->score = 1;
769 // end Replace
770 $answer->answer = $dataanswer;
771 $answer->response = $question->feedback[$key];
772 if (!$answer->id = insert_record("lesson_answers", $answer)) {
773 $result->error = "Could not insert multichoice quiz answer! ";
774 return $result;
776 // for Sanity checks
777 if ($question->fraction[$key] > 0) {
778 $totalfraction += $question->fraction[$key];
780 if ($question->fraction[$key] > $maxfraction) {
781 $maxfraction = $question->fraction[$key];
786 /// Perform sanity checks on fractional grades
787 if ($question->single) {
788 if ($maxfraction != 1) {
789 $maxfraction = $maxfraction * 100;
790 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
791 return $result;
793 } else {
794 $totalfraction = round($totalfraction,2);
795 if ($totalfraction != 1) {
796 $totalfraction = $totalfraction * 100;
797 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
798 return $result;
801 break;
803 case LESSON_MATCHING:
805 $subquestions = array();
807 $i = 0;
808 // Insert all the new question+answer pairs
809 foreach ($question->subquestions as $key => $questiontext) {
810 $answertext = $question->subanswers[$key];
811 if (!empty($questiontext) and !empty($answertext)) {
812 $answer = new stdClass;
813 $answer->lessonid = $question->lessonid;
814 $answer->pageid = $question->id;
815 $answer->timecreated = $timenow;
816 $answer->answer = $questiontext;
817 $answer->response = $answertext;
818 if ($i == 0) {
819 // first answer contains the correct answer jump
820 $answer->jumpto = LESSON_NEXTPAGE;
822 if (!$subquestion->id = insert_record("lesson_answers", $answer)) {
823 $result->error = "Could not insert quiz match subquestion!";
824 return $result;
826 $subquestions[] = $subquestion->id;
827 $i++;
831 if (count($subquestions) < 3) {
832 $result->notice = get_string("notenoughsubquestions", "quiz");
833 return $result;
836 break;
839 case LESSON_RANDOMSAMATCH:
840 $options->question = $question->id;
841 $options->choose = $question->choose;
842 if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) {
843 $options->id = $existing->id;
844 if (!update_record("quiz_randomsamatch", $options)) {
845 $result->error = "Could not update quiz randomsamatch options!";
846 return $result;
848 } else {
849 if (!insert_record("quiz_randomsamatch", $options)) {
850 $result->error = "Could not insert quiz randomsamatch options!";
851 return $result;
854 break;
856 case LESSON_MULTIANSWER:
857 if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
858 $oldmultianswers = array();
861 // Insert all the new multi answers
862 foreach ($question->answers as $dataanswer) {
863 if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
864 $multianswer = $oldmultianswer;
865 $multianswer->positionkey = $dataanswer->positionkey;
866 $multianswer->norm = $dataanswer->norm;
867 $multianswer->answertype = $dataanswer->answertype;
869 if (! $multianswer->answers = quiz_save_multianswer_alternatives
870 ($question->id, $dataanswer->answertype,
871 $dataanswer->alternatives, $oldmultianswer->answers))
873 $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
874 return $result;
876 if (!update_record("quiz_multianswers", $multianswer)) {
877 $result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
878 return $result;
880 } else { // This is a completely new answer
881 $multianswer = new stdClass;
882 $multianswer->question = $question->id;
883 $multianswer->positionkey = $dataanswer->positionkey;
884 $multianswer->norm = $dataanswer->norm;
885 $multianswer->answertype = $dataanswer->answertype;
887 if (! $multianswer->answers = quiz_save_multianswer_alternatives
888 ($question->id, $dataanswer->answertype,
889 $dataanswer->alternatives))
891 $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
892 return $result;
894 if (!insert_record("quiz_multianswers", $multianswer)) {
895 $result->error = "Could not insert quiz multianswer!";
896 return $result;
900 break;
902 case LESSON_RANDOM:
903 break;
905 case LESSON_DESCRIPTION:
906 break;
908 default:
909 $result->error = "Unsupported question type ($question->qtype)!";
910 return $result;
911 break;
913 return true;
917 * Determins if a jumpto value is correct or not.
919 * returns true if jumpto page is (logically) after the pageid page or
920 * if the jumpto value is a special value. Returns false in all other cases.
922 * @param int $pageid Id of the page from which you are jumping from.
923 * @param int $jumpto The jumpto number.
924 * @return boolean True or false after a series of tests.
926 function lesson_iscorrect($pageid, $jumpto) {
928 // first test the special values
929 if (!$jumpto) {
930 // same page
931 return false;
932 } elseif ($jumpto == LESSON_NEXTPAGE) {
933 return true;
934 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
935 return true;
936 } elseif ($jumpto == LESSON_RANDOMPAGE) {
937 return true;
938 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
939 return true;
940 } elseif ($jumpto == LESSON_EOL) {
941 return true;
943 // we have to run through the pages from pageid looking for jumpid
944 if ($lessonid = get_field('lesson_pages', 'lessonid', 'id', $pageid)) {
945 if ($pages = get_records('lesson_pages', 'lessonid', $lessonid, '', 'id, nextpageid')) {
946 $apageid = $pages[$pageid]->nextpageid;
947 while ($apageid != 0) {
948 if ($jumpto == $apageid) {
949 return true;
951 $apageid = $pages[$apageid]->nextpageid;
955 return false;
959 * Checks to see if a page is a branch table or is
960 * a page that is enclosed by a branch table and an end of branch or end of lesson.
961 * May call this function: {@link lesson_is_page_in_branch()}
963 * @param int $lesson Id of the lesson to which the page belongs.
964 * @param int $pageid Id of the page.
965 * @return boolean True or false.
967 function lesson_display_branch_jumps($lessonid, $pageid) {
968 if($pageid == 0) {
969 // first page
970 return false;
972 // get all of the lesson pages
973 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
974 // adding first page
975 return false;
978 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
979 return true;
982 return lesson_is_page_in_branch($lessonpages, $pageid);
986 * Checks to see if a page is a cluster page or is
987 * a page that is enclosed by a cluster page and an end of cluster or end of lesson
988 * May call this function: {@link lesson_is_page_in_cluster()}
990 * @param int $lesson Id of the lesson to which the page belongs.
991 * @param int $pageid Id of the page.
992 * @return boolean True or false.
994 function lesson_display_cluster_jump($lesson, $pageid) {
995 if($pageid == 0) {
996 // first page
997 return false;
999 // get all of the lesson pages
1000 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1001 // adding first page
1002 return false;
1005 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1006 return true;
1009 return lesson_is_page_in_cluster($lessonpages, $pageid);
1014 * Checks to see if a LESSON_CLUSTERJUMP or
1015 * a LESSON_UNSEENBRANCHPAGE is used in a lesson.
1017 * This function is only executed when a teacher is
1018 * checking the navigation for a lesson.
1020 * @param int $lesson Id of the lesson that is to be checked.
1021 * @return boolean True or false.
1023 function lesson_display_teacher_warning($lesson) {
1024 // get all of the lesson answers
1025 if (!$lessonanswers = get_records_select("lesson_answers", "lessonid = $lesson")) {
1026 // no answers, then not useing cluster or unseen
1027 return false;
1029 // just check for the first one that fulfills the requirements
1030 foreach ($lessonanswers as $lessonanswer) {
1031 if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) {
1032 return true;
1036 // if no answers use either of the two jumps
1037 return false;
1042 * Interprets LESSON_CLUSTERJUMP jumpto value.
1044 * This will select a page randomly
1045 * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
1046 * and the page selected will be a page that has not been viewed already
1047 * and if any pages are within a branch table or end of branch then only 1 page within
1048 * the branch table or end of branch will be randomly selected (sub clustering).
1050 * @param int $lessonid Id of the lesson.
1051 * @param int $userid Id of the user.
1052 * @param int $pageid Id of the current page from which we are jumping from.
1053 * @return int The id of the next page.
1055 function lesson_cluster_jump($lessonid, $userid, $pageid) {
1056 // get the number of retakes
1057 if (!$retakes = count_records("lesson_grades", "lessonid", $lessonid, "userid", $userid)) {
1058 $retakes = 0;
1061 // get all the lesson_attempts aka what the user has seen
1062 if ($seen = get_records_select("lesson_attempts", "lessonid = $lessonid AND userid = $userid AND retry = $retakes", "timeseen DESC")) {
1063 foreach ($seen as $value) { // load it into an array that I can more easily use
1064 $seenpages[$value->pageid] = $value->pageid;
1066 } else {
1067 $seenpages = array();
1070 // get the lesson pages
1071 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1072 error("Error: could not find records in lesson_pages table");
1074 // find the start of the cluster
1075 while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
1076 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1077 break;
1079 $pageid = $lessonpages[$pageid]->prevpageid;
1082 $pageid = $lessonpages[$pageid]->nextpageid; // move down from the cluster page
1084 $clusterpages = array();
1085 while (true) { // now load all the pages into the cluster that are not already inside of a branch table.
1086 if ($lessonpages[$pageid]->qtype == LESSON_ENDOFCLUSTER) {
1087 // store the endofcluster page's jump
1088 $exitjump = get_field("lesson_answers", "jumpto", "pageid", $pageid, "lessonid", $lessonid);
1089 if ($exitjump == LESSON_NEXTPAGE) {
1090 $exitjump = $lessonpages[$pageid]->nextpageid;
1092 if ($exitjump == 0) {
1093 $exitjump = LESSON_EOL;
1095 break;
1096 } elseif (!lesson_is_page_in_branch($lessonpages, $pageid) && $lessonpages[$pageid]->qtype != LESSON_ENDOFBRANCH) {
1097 // load page into array when it is not in a branch table and when it is not an endofbranch
1098 $clusterpages[] = $lessonpages[$pageid];
1100 if ($lessonpages[$pageid]->nextpageid == 0) {
1101 // shouldn't ever get here... should be using endofcluster
1102 $exitjump = LESSON_EOL;
1103 break;
1104 } else {
1105 $pageid = $lessonpages[$pageid]->nextpageid;
1109 // filter out the ones we have seen
1110 $unseen = array();
1111 foreach ($clusterpages as $clusterpage) {
1112 if ($clusterpage->qtype == LESSON_BRANCHTABLE) { // if branchtable, check to see if any pages inside have been viewed
1113 $branchpages = lesson_pages_in_branch($lessonpages, $clusterpage->id); // get the pages in the branchtable
1114 $flag = true;
1115 foreach ($branchpages as $branchpage) {
1116 if (array_key_exists($branchpage->id, $seenpages)) { // check if any of the pages have been viewed
1117 $flag = false;
1120 if ($flag && count($branchpages) > 0) {
1121 // add branch table
1122 $unseen[] = $clusterpage;
1124 } else {
1125 // add any other type of page that has not already been viewed
1126 if (!array_key_exists($clusterpage->id, $seenpages)) {
1127 $unseen[] = $clusterpage;
1132 if (count($unseen) > 0) { // it does not contain elements, then use exitjump, otherwise find out next page/branch
1133 $nextpage = $unseen[rand(0, count($unseen)-1)];
1134 } else {
1135 return $exitjump; // seen all there is to see, leave the cluster
1138 if ($nextpage->qtype == LESSON_BRANCHTABLE) { // if branch table, then pick a random page inside of it
1139 $branchpages = lesson_pages_in_branch($lessonpages, $nextpage->id);
1140 return $branchpages[rand(0, count($branchpages)-1)]->id;
1141 } else { // otherwise, return the page's id
1142 return $nextpage->id;
1147 * Returns pages that are within a branch table and another branch table, end of branch or end of lesson
1149 * @param array $lessonpages An array of lesson page objects.
1150 * @param int $branchid The id of the branch table that we would like the containing pages for.
1151 * @return array An array of lesson page objects.
1153 function lesson_pages_in_branch($lessonpages, $branchid) {
1154 $pageid = $lessonpages[$branchid]->nextpageid; // move to the first page after the branch table
1155 $pagesinbranch = array();
1157 while (true) {
1158 if ($pageid == 0) { // EOL
1159 break;
1160 } elseif ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1161 break;
1162 } elseif ($lessonpages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1163 break;
1165 $pagesinbranch[] = $lessonpages[$pageid];
1166 $pageid = $lessonpages[$pageid]->nextpageid;
1169 return $pagesinbranch;
1173 * Interprets the LESSON_UNSEENBRANCHPAGE jump.
1175 * will return the pageid of a random unseen page that is within a branch
1177 * @see lesson_pages_in_branch()
1178 * @param int $lesson Id of the lesson.
1179 * @param int $userid Id of the user.
1180 * @param int $pageid Id of the page from which we are jumping.
1181 * @return int Id of the next page.
1183 function lesson_unseen_question_jump($lesson, $user, $pageid) {
1184 // get the number of retakes
1185 if (!$retakes = count_records("lesson_grades", "lessonid", $lesson, "userid", $user)) {
1186 $retakes = 0;
1189 // get all the lesson_attempts aka what the user has seen
1190 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson AND userid = $user AND retry = $retakes", "timeseen DESC")) {
1191 foreach($viewedpages as $viewed) {
1192 $seenpages[] = $viewed->pageid;
1194 } else {
1195 $seenpages = array();
1198 // get the lesson pages
1199 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1200 error("Error: could not find records in lesson_pages table");
1203 if ($pageid == LESSON_UNSEENBRANCHPAGE) { // this only happens when a student leaves in the middle of an unseen question within a branch series
1204 $pageid = $seenpages[0]; // just change the pageid to the last page viewed inside the branch table
1207 // go up the pages till branch table
1208 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1209 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1210 break;
1212 $pageid = $lessonpages[$pageid]->prevpageid;
1215 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1217 // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array
1218 $unseen = array();
1219 foreach($pagesinbranch as $page) {
1220 if (!in_array($page->id, $seenpages)) {
1221 $unseen[] = $page->id;
1225 if(count($unseen) == 0) {
1226 if(isset($pagesinbranch)) {
1227 $temp = end($pagesinbranch);
1228 $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL
1229 } else {
1230 // there are no pages inside the branch, so return the next page
1231 $nextpage = $lessonpages[$pageid]->nextpageid;
1233 if ($nextpage == 0) {
1234 return LESSON_EOL;
1235 } else {
1236 return $nextpage;
1238 } else {
1239 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1244 * Handles the unseen branch table jump.
1246 * @param int $lessonid Lesson id.
1247 * @param int $userid User id.
1248 * @return int Will return the page id of a branch table or end of lesson
1250 function lesson_unseen_branch_jump($lessonid, $userid) {
1251 if (!$retakes = count_records("lesson_grades", "lessonid", $lessonid, "userid", $userid)) {
1252 $retakes = 0;
1255 if (!$seenbranches = get_records_select("lesson_branch", "lessonid = $lessonid AND userid = $userid AND retry = $retakes",
1256 "timeseen DESC")) {
1257 error("Error: could not find records in lesson_branch table");
1260 // get the lesson pages
1261 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1262 error("Error: could not find records in lesson_pages table");
1265 // this loads all the viewed branch tables into $seen untill it finds the branch table with the flag
1266 // which is the branch table that starts the unseenbranch function
1267 $seen = array();
1268 foreach ($seenbranches as $seenbranch) {
1269 if (!$seenbranch->flag) {
1270 $seen[$seenbranch->pageid] = $seenbranch->pageid;
1271 } else {
1272 $start = $seenbranch->pageid;
1273 break;
1276 // this function searches through the lesson pages to find all the branch tables
1277 // that follow the flagged branch table
1278 $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table
1279 while ($pageid != 0) { // grab all of the branch table till eol
1280 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1281 $branchtables[] = $lessonpages[$pageid]->id;
1283 $pageid = $lessonpages[$pageid]->nextpageid;
1285 $unseen = array();
1286 foreach ($branchtables as $branchtable) {
1287 // load all of the unseen branch tables into unseen
1288 if (!array_key_exists($branchtable, $seen)) {
1289 $unseen[] = $branchtable;
1292 if (count($unseen) > 0) {
1293 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1294 } else {
1295 return LESSON_EOL; // has viewed all of the branch tables
1300 * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE).
1302 * @param int $lessonid Lesson id.
1303 * @param int $pageid The id of the page that we are jumping from (?)
1304 * @return int The pageid of a random page that is within a branch table
1306 function lesson_random_question_jump($lessonid, $pageid) {
1307 // get the lesson pages
1308 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1309 error("Error: could not find records in lesson_pages table");
1312 // go up the pages till branch table
1313 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1315 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1316 break;
1318 $pageid = $lessonpages[$pageid]->prevpageid;
1321 // get the pages within the branch
1322 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1324 if(count($pagesinbranch) == 0) {
1325 // there are no pages inside the branch, so return the next page
1326 return $lessonpages[$pageid]->nextpageid;
1327 } else {
1328 return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id; // returns a random page id for the next page
1333 * Check to see if a page is below a branch table (logically).
1335 * Will return true if a branch table is found logically above the page.
1336 * Will return false if an end of branch, cluster or the beginning
1337 * of the lesson is found before a branch table.
1339 * @param array $pages An array of lesson page objects.
1340 * @param int $pageid Id of the page for testing.
1341 * @return boolean
1343 function lesson_is_page_in_branch($pages, $pageid) {
1344 $pageid = $pages[$pageid]->prevpageid; // move up one
1346 // go up the pages till branch table
1347 while (true) {
1348 if ($pageid == 0) { // ran into the beginning of the lesson
1349 return false;
1350 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFBRANCH) { // ran into the end of another branch table
1351 return false;
1352 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // do not look beyond a cluster
1353 return false;
1354 } elseif ($pages[$pageid]->qtype == LESSON_BRANCHTABLE) { // hit a branch table
1355 return true;
1357 $pageid = $pages[$pageid]->prevpageid;
1363 * Check to see if a page is below a cluster page (logically).
1365 * Will return true if a cluster is found logically above the page.
1366 * Will return false if an end of cluster or the beginning
1367 * of the lesson is found before a cluster page.
1369 * @param array $pages An array of lesson page objects.
1370 * @param int $pageid Id of the page for testing.
1371 * @return boolean
1373 function lesson_is_page_in_cluster($pages, $pageid) {
1374 $pageid = $pages[$pageid]->prevpageid; // move up one
1376 // go up the pages till branch table
1377 while (true) {
1378 if ($pageid == 0) { // ran into the beginning of the lesson
1379 return false;
1380 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER) { // ran into the end of another branch table
1381 return false;
1382 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // hit a branch table
1383 return true;
1385 $pageid = $pages[$pageid]->prevpageid;
1390 * Calculates a user's grade for a lesson.
1392 * @param object $lesson The lesson that the user is taking.
1393 * @param int $retries The attempt number.
1394 * @param int $userid Id of the user (optinal, default current user).
1395 * @return object { nquestions => number of questions answered
1396 attempts => number of question attempts
1397 total => max points possible
1398 earned => points earned by student
1399 grade => calculated percentage grade
1400 nmanual => number of manually graded questions
1401 manualpoints => point value for manually graded questions }
1403 function lesson_grade($lesson, $ntries, $userid = 0) {
1404 global $USER;
1406 if (empty($userid)) {
1407 $userid = $USER->id;
1410 // Zero out everything
1411 $ncorrect = 0;
1412 $nviewed = 0;
1413 $score = 0;
1414 $nmanual = 0;
1415 $manualpoints = 0;
1416 $thegrade = 0;
1417 $nquestions = 0;
1418 $total = 0;
1419 $earned = 0;
1421 if ($useranswers = get_records_select("lesson_attempts", "lessonid = $lesson->id AND
1422 userid = $userid AND retry = $ntries", "timeseen")) {
1423 // group each try with its page
1424 $attemptset = array();
1425 foreach ($useranswers as $useranswer) {
1426 $attemptset[$useranswer->pageid][] = $useranswer;
1429 // Drop all attempts that go beyond max attempts for the lesson
1430 foreach ($attemptset as $key => $set) {
1431 $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts);
1434 $pageids = implode(",", array_keys($attemptset));
1436 // get only the pages and their answers that the user answered
1437 $pages = get_records_select("lesson_pages", "lessonid = $lesson->id AND id IN($pageids)");
1438 $answers = get_records_select("lesson_answers", "lessonid = $lesson->id AND pageid IN($pageids)");
1440 // Number of pages answered
1441 $nquestions = count($pages);
1443 foreach ($attemptset as $attempts) {
1444 if ($lesson->custom) {
1445 $attempt = end($attempts);
1446 // If essay question, handle it, otherwise add to score
1447 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1448 $essayinfo = unserialize($attempt->useranswer);
1449 $earned += $essayinfo->score;
1450 $nmanual++;
1451 $manualpoints += $answers[$attempt->answerid]->score;
1452 } else {
1453 $earned += $answers[$attempt->answerid]->score;
1455 } else {
1456 foreach ($attempts as $attempt) {
1457 $earned += $attempt->correct;
1459 $attempt = end($attempts); // doesn't matter which one
1460 // If essay question, increase numbers
1461 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1462 $nmanual++;
1463 $manualpoints++;
1466 // Number of times answered
1467 $nviewed += count($attempts);
1470 if ($lesson->custom) {
1471 $bestscores = array();
1472 // Find the highest possible score per page to get our total
1473 foreach ($answers as $answer) {
1474 if(!isset($bestscores[$answer->pageid])) {
1475 $bestscores[$answer->pageid] = $answer->score;
1476 } else if ($bestscores[$answer->pageid] < $answer->score) {
1477 $bestscores[$answer->pageid] = $answer->score;
1480 $total = array_sum($bestscores);
1481 } else {
1482 // Check to make sure the student has answered the minimum questions
1483 if ($lesson->minquestions and $nquestions < $lesson->minquestions) {
1484 // Nope, increase number viewed by the amount of unanswered questions
1485 $total = $nviewed + ($lesson->minquestions - $nquestions);
1486 } else {
1487 $total = $nviewed;
1492 if ($total) { // not zero
1493 $thegrade = round(100 * $earned / $total, 5);
1496 // Build the grade information object
1497 $gradeinfo = new stdClass;
1498 $gradeinfo->nquestions = $nquestions;
1499 $gradeinfo->attempts = $nviewed;
1500 $gradeinfo->total = $total;
1501 $gradeinfo->earned = $earned;
1502 $gradeinfo->grade = $thegrade;
1503 $gradeinfo->nmanual = $nmanual;
1504 $gradeinfo->manualpoints = $manualpoints;
1506 return $gradeinfo;
1510 * Prints the on going message to the user.
1512 * With custom grading On, displays points
1513 * earned out of total points possible thus far.
1514 * With custom grading Off, displays number of correct
1515 * answers out of total attempted.
1517 * @param object $lesson The lesson that the user is taking.
1518 * @return void
1520 function lesson_print_ongoing_score($lesson) {
1521 global $USER;
1522 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1523 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1525 if (has_capability('mod/lesson:manage', $context)) {
1526 echo "<p align=\"center\">".get_string('teacherongoingwarning', 'lesson').'</p>';
1527 } else {
1528 $ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id);
1529 if (isset($USER->modattempts[$lesson->id])) {
1530 $ntries--;
1532 $gradeinfo = lesson_grade($lesson, $ntries);
1534 $a = new stdClass;
1535 if ($lesson->custom) {
1536 $a->score = $gradeinfo->earned;
1537 $a->currenthigh = $gradeinfo->total;
1538 print_simple_box(get_string("ongoingcustom", "lesson", $a), "center");
1539 } else {
1540 $a->correct = $gradeinfo->earned;
1541 $a->viewed = $gradeinfo->attempts;
1542 print_simple_box(get_string("ongoingnormal", "lesson", $a), "center");
1548 * Prints tabs for the editing and adding pages. Each tab is a question type.
1550 * @param array $qtypes The question types array (may not need to pass this because it is defined in this file)
1551 * @param string $selected Current selected tab
1552 * @param string $link The base href value of the link for the tab
1553 * @param string $onclick Javascript for the tab link
1554 * @return void
1556 function lesson_qtype_menu($qtypes, $selected="", $link="", $onclick="") {
1557 $tabs = array();
1558 $tabrows = array();
1560 foreach ($qtypes as $qtype => $qtypename) {
1561 $tabrows[] = new tabobject($qtype, "$link&amp;qtype=$qtype\" onclick=\"$onclick", $qtypename);
1563 $tabs[] = $tabrows;
1564 print_tabs($tabs, $selected);
1565 echo "<input type=\"hidden\" name=\"qtype\" value=\"$selected\" /> \n";
1570 * Prints out a Progress Bar which depicts a user's progress within a lesson.
1572 * Currently works best with a linear lesson. Clusters are counted as a single page.
1573 * Also, only viewed branch tables and questions that have been answered correctly count
1574 * toward lesson completion (or progress). Only Students can see the Progress bar as well.
1576 * @param object $lesson The lesson that the user is currently taking.
1577 * @param object $course The course that the to which the lesson belongs.
1578 * @return boolean The return is not significant as of yet. Will return true/false.
1580 function lesson_print_progress_bar($lesson, $course) {
1581 global $CFG, $USER;
1582 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1583 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1585 // lesson setting to turn progress bar on or off
1586 if (!$lesson->progressbar) {
1587 return false;
1590 // catch teachers
1591 if (has_capability('mod/lesson:manage', $context)) {
1592 notify(get_string('progressbarteacherwarning', 'lesson', $course->teachers));
1593 return false;
1595 if (!isset($USER->modattempts[$lesson->id])) {
1596 // all of the lesson pages
1597 if (!$pages = get_records('lesson_pages', 'lessonid', $lesson->id)) {
1598 return false;
1599 } else {
1600 foreach ($pages as $page) {
1601 if ($page->prevpageid == 0) {
1602 $pageid = $page->id; // find the first page id
1603 break;
1608 // current attempt number
1609 if (!$ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id)) {
1610 $ntries = 0; // may not be necessary
1613 $viewedpageids = array();
1615 // collect all of the correctly answered questions
1616 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries AND correct = 1", 'timeseen DESC', 'pageid, id')) {
1617 $viewedpageids = array_keys($viewedpages);
1619 // collect all of the branch tables viewed
1620 if ($viewedbranches = get_records_select("lesson_branch", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries", 'timeseen DESC', 'pageid, id')) {
1621 $viewedpageids = array_merge($viewedpageids, array_keys($viewedbranches));
1624 // Filter out the following pages:
1625 // End of Cluster
1626 // End of Branch
1627 // Pages found inside of Clusters
1628 // Do not filter out Cluster Page(s) because we count a cluster as one.
1629 // By keeping the cluster page, we get our 1
1630 $validpages = array();
1631 while ($pageid != 0) {
1632 if ($pages[$pageid]->qtype == LESSON_CLUSTER) {
1633 $clusterpageid = $pageid; // copy it
1634 $validpages[$clusterpageid] = 1; // add the cluster page as a valid page
1635 $pageid = $pages[$pageid]->nextpageid; // get next page
1637 // now, remove all necessary viewed paged ids from the viewedpageids array.
1638 while ($pages[$pageid]->qtype != LESSON_ENDOFCLUSTER and $pageid != 0) {
1639 if (in_array($pageid, $viewedpageids)) {
1640 unset($viewedpageids[array_search($pageid, $viewedpageids)]); // remove it
1641 // since the user did see one page in the cluster, add the cluster pageid to the viewedpageids
1642 if (!in_array($clusterpageid, $viewedpageids)) {
1643 $viewedpageids[] = $clusterpageid;
1646 $pageid = $pages[$pageid]->nextpageid;
1648 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER or $pages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1649 // dont count these, just go to next
1650 $pageid = $pages[$pageid]->nextpageid;
1651 } else {
1652 // a counted page
1653 $validpages[$pageid] = 1;
1654 $pageid = $pages[$pageid]->nextpageid;
1658 // progress calculation as a percent
1659 $progress = round(count($viewedpageids)/count($validpages), 2) * 100;
1660 } else {
1661 $progress = 100;
1664 // print out the Progress Bar. Attempted to put as much as possible in the style sheets.
1665 echo '<div class="progress_bar" align="center">';
1666 echo '<table class="progress_bar_table"><tr>';
1667 if ($progress != 0) { // some browsers do not repsect the 0 width.
1668 echo '<td style="width:'.$progress.'%;" class="progress_bar_completed">';
1669 echo '</td>';
1671 echo '<td class="progress_bar_todo">';
1672 echo '<div class="progress_bar_token"></div>';
1673 echo '</td>';
1674 echo '</tr></table>';
1675 echo '</div>';
1677 return true;
1681 * Determines if a user can view the left menu. The determining factor
1682 * is whether a user has a grade greater than or equal to the lesson setting
1683 * of displayleftif
1685 * @param object $lesson Lesson object of the current lesson
1686 * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged
1688 function lesson_displayleftif($lesson) {
1689 global $CFG, $USER;
1691 if (!empty($lesson->displayleftif)) {
1692 // get the current user's max grade for this lesson
1693 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')) {
1694 if ($maxgrade->maxgrade < $lesson->displayleftif) {
1695 return 0; // turn off the displayleft
1697 } else {
1698 return 0; // no grades
1702 // if we get to here, keep the original state of displayleft lesson setting
1703 return $lesson->displayleft;
1707 * If there is a media file associated with this
1708 * lesson, then print it in a block.
1710 * @param int $cmid Course Module ID for this lesson
1711 * @param object $lesson Full lesson record object
1712 * @return void
1714 function lesson_print_mediafile_block($cmid, $lesson) {
1715 if (!empty($lesson->mediafile)) {
1716 $url = '/mod/lesson/mediafile.php?id='.$cmid;
1717 $options = 'menubar=0,location=0,left=5,top=5,scrollbars,resizable,width='. $lesson->mediawidth .',height='. $lesson->mediaheight;
1718 $name = 'lessonmediafile';
1720 $content = link_to_popup_window ($url, $name, get_string('mediafilepopup', 'lesson'), '', '', get_string('mediafilepopup', 'lesson'), $options, true);
1721 $content .= helpbutton("mediafilestudent", get_string("mediafile", "lesson"), "lesson", true, false, '', true);
1723 print_side_block(get_string('linkedmedia', 'lesson'), $content, NULL, NULL, '', array('class' => 'mediafile'), get_string('linkedmedia', 'lesson'));
1728 * If a timed lesson and not a teacher, then
1729 * print the clock
1731 * @param int $cmid Course Module ID for this lesson
1732 * @param object $lesson Full lesson record object
1733 * @param object $timer Full timer record object
1734 * @return void
1736 function lesson_print_clock_block($cmid, $lesson, $timer) {
1737 global $CFG;
1739 $context = get_context_instance(CONTEXT_MODULE, $cmid);
1741 // Display for timed lessons and for students only
1742 if($lesson->timed and !has_capability('mod/lesson:manage', $context) and !empty($timer)) {
1743 $content = '<script type="text/javascript" charset="utf-8">'."\n";
1744 $content .= "<!--\n";
1745 $content .= ' var starttime = '.$timer->starttime.";\n";
1746 $content .= ' var servertime = '.time().";\n";
1747 $content .= ' var testlength = '.($lesson->maxtime * 60).";\n";
1748 $content .= ' document.write(\'<script type="text/javascript" src="'.$CFG->wwwroot.'/mod/lesson/timer.js" charset="utf-8"><\/script>\');'."\n";
1749 $content .= " window.onload = function () { show_clock(); };\n";
1750 $content .= "// -->\n";
1751 $content .= "</script>\n";
1752 $content .= "<noscript>\n";
1753 $content .= lesson_print_time_remaining($timer->starttime, $lesson->maxtime, true)."\n";
1754 $content .= "</noscript>\n";
1756 print_side_block(get_string('timeremaining', 'lesson'), $content, NULL, NULL, '', array('class' => 'clock'), get_string('timeremaining', 'lesson'));
1761 * If left menu is turned on, then this will
1762 * print the menu in a block
1764 * @param int $cmid Course Module ID for this lesson
1765 * @param object $lesson Full lesson record object
1766 * @return void
1768 function lesson_print_menu_block($cmid, $lesson) {
1769 global $CFG;
1771 if ($lesson->displayleft) {
1772 $pageid = get_field('lesson_pages', 'id', 'lessonid', $lesson->id, 'prevpageid', 0);
1773 $pages = get_records_select('lesson_pages', "lessonid = $lesson->id");
1774 $currentpageid = optional_param('pageid', $pageid, PARAM_INT);
1776 if ($pageid and $pages) {
1777 $content = '<a href="#maincontent" class="skip">'.get_string('skip', 'lesson')."</a>\n<div class=\"menuwrapper\">\n<ul>\n";
1779 while ($pageid != 0) {
1780 $page = $pages[$pageid];
1782 // Only process branch tables with display turned on
1783 if ($page->qtype == LESSON_BRANCHTABLE and $page->display) {
1784 if ($page->id == $currentpageid) {
1785 $content .= '<li class="selected">'.format_string($page->title,true)."</a></li>\n";
1786 } else {
1787 $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";
1791 $pageid = $page->nextpageid;
1793 $content .= "</ul>\n</div>\n";
1794 print_side_block(get_string('lessonmenu', 'lesson'), $content, NULL, NULL, '', array('class' => 'menu'), get_string('lessonmenu', 'lesson'));
1800 * This is not ideal, but checks to see if a
1801 * column has "block" content.
1803 * In the future, it would be nice if the lesson
1804 * media file, timer and navigation were blocks
1805 * then this would be unnecessary.
1807 * @uses $CFG
1808 * @uses $PAGE
1809 * @param object $lesson Full lesson record object
1810 * @param array $pageblocks An array of block instances organized by left and right columns
1811 * @param string $column Pass either BLOCK_POS_RIGHT or BLOCK_POS_LEFT constants
1812 * @return boolean
1814 function lesson_blocks_have_content($lesson, $pageblocks, $column) {
1815 global $CFG, $PAGE;
1817 // First check lesson conditions
1818 if ($column == BLOCK_POS_RIGHT) {
1819 $managecap = false;
1820 if ($cm = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)) {
1821 $managecap = has_capability('mod/lesson:manage', get_context_instance(CONTEXT_MODULE, $cm->id));
1823 if (($lesson->timed and !$managecap) or !empty($lesson->mediafile)) {
1824 return true;
1826 } else if ($column == BLOCK_POS_LEFT) {
1827 if ($lesson->displayleft) {
1828 return true;
1831 if (!empty($CFG->showblocksonmodpages)) {
1832 if ((blocks_have_content($pageblocks, $column) || $PAGE->user_is_editing())) {
1833 return true;
1837 return false;