Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / mod / lesson / locallib.php
blobb6ab81fe8aa818f37ccf27ee08be17d669013b2c
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 $strlesson = get_string('modulename', 'lesson');
230 $strname = format_string($lesson->name, true, $course->id);
232 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
234 if (has_capability('mod/lesson:edit', $context)) {
235 $button = update_module_button($cm->id, $course->id, $strlesson);
236 } else {
237 $button = '';
240 /// Header setup
241 $navigation = build_navigation('', $cm);
243 /// Print header, heading, tabs and messages
244 print_header("$course->shortname: $strname", $course->fullname, $navigation,
245 '', '', true, $button, navmenu($course, $cm));
247 if (has_capability('mod/lesson:manage', $context)) {
248 print_heading_with_help($strname, "overview", "lesson");
250 if (!empty($currenttab)) {
251 include($CFG->dirroot.'/mod/lesson/tabs.php');
253 } else {
254 print_heading($strname);
257 lesson_print_messages();
259 return true;
263 * Returns course module, course and module instance given
264 * either the course module ID or a lesson module ID.
266 * @param int $cmid Course Module ID
267 * @param int $lessonid Lesson module instance ID
268 * @return array array($cm, $course, $lesson)
270 function lesson_get_basics($cmid = 0, $lessonid = 0) {
271 if ($cmid) {
272 if (!$cm = get_coursemodule_from_id('lesson', $cmid)) {
273 error('Course Module ID was incorrect');
275 if (!$course = get_record('course', 'id', $cm->course)) {
276 error('Course is misconfigured');
278 if (!$lesson = get_record('lesson', 'id', $cm->instance)) {
279 error('Course module is incorrect');
281 } else if ($lessonid) {
282 if (!$lesson = get_record('lesson', 'id', $lessonid)) {
283 error('Course module is incorrect');
285 if (!$course = get_record('course', 'id', $lesson->course)) {
286 error('Course is misconfigured');
288 if (!$cm = get_coursemodule_from_instance('lesson', $lesson->id, $course->id)) {
289 error('Course Module ID was incorrect');
291 } else {
292 error('No course module ID or lesson ID were passed');
295 return array($cm, $course, $lesson);
299 * Sets a message to be printed. Messages are printed
300 * by calling {@link lesson_print_messages()}.
302 * @uses $SESSION
303 * @param string $message The message to be printed
304 * @param string $class Class to be passed to {@link notify()}. Usually notifyproblem or notifysuccess.
305 * @param string $align Alignment of the message
306 * @return boolean
308 function lesson_set_message($message, $class="notifyproblem", $align='center') {
309 global $SESSION;
311 if (empty($SESSION->lesson_messages) or !is_array($SESSION->lesson_messages)) {
312 $SESSION->lesson_messages = array();
315 $SESSION->lesson_messages[] = array($message, $class, $align);
317 return true;
321 * Print all set messages.
323 * See {@link lesson_set_message()} for setting messages.
325 * Uses {@link notify()} to print the messages.
327 * @uses $SESSION
328 * @return boolean
330 function lesson_print_messages() {
331 global $SESSION;
333 if (empty($SESSION->lesson_messages)) {
334 // No messages to print
335 return true;
338 foreach($SESSION->lesson_messages as $message) {
339 notify($message[0], $message[1], $message[2]);
342 // Reset
343 unset($SESSION->lesson_messages);
345 return true;
349 * Prints a lesson link that submits a form.
351 * If Javascript is disabled, then a regular submit button is printed
353 * @param string $name Name of the link or button
354 * @param string $form The name of the form to be submitted
355 * @param string $align Alignment of the button
356 * @param string $class Class names to add to the div wrapper
357 * @param string $title Title for the link (Not used if javascript is disabled)
358 * @param string $id ID tag
359 * @param boolean $return Return flag
360 * @return mixed boolean/html
362 function lesson_print_submit_link($name, $form, $align = 'center', $class='standardbutton', $title = '', $id = '', $return = false) {
363 if (!empty($align)) {
364 $align = " style=\"text-align:$align\"";
366 if (!empty($id)) {
367 $id = " id=\"$id\"";
369 if (empty($title)) {
370 $title = $name;
373 $output = "<div class=\"lessonbutton $class\" $align>\n";
374 $output .= "<input type=\"submit\" value=\"$name\" $align $id />";
375 $output .= "</div>\n";
377 if ($return) {
378 return $output;
379 } else {
380 echo $output;
381 return true;
386 * Prints a time remaining in the following format: H:MM:SS
388 * @param int $starttime Time when the lesson started
389 * @param int $maxtime Length of the lesson
390 * @param boolean $return Return output switch
391 * @return mixed boolean/string
393 function lesson_print_time_remaining($starttime, $maxtime, $return = false) {
394 // Calculate hours, minutes and seconds
395 $timeleft = $starttime + $maxtime * 60 - time();
396 $hours = floor($timeleft/3600);
397 $timeleft = $timeleft - ($hours * 3600);
398 $minutes = floor($timeleft/60);
399 $secs = $timeleft - ($minutes * 60);
401 if ($minutes < 10) {
402 $minutes = "0$minutes";
404 if ($secs < 10) {
405 $secs = "0$secs";
407 $output = array();
408 $output[] = $hours;
409 $output[] = $minutes;
410 $output[] = $secs;
412 $output = implode(':', $output);
414 if ($return) {
415 return $output;
416 } else {
417 echo $output;
418 return true;
423 * Prints the page action buttons
425 * Move/Edit/Preview/Delete
427 * @uses $CFG
428 * @param int $cmid Course Module ID
429 * @param object $page Page record
430 * @param boolean $printmove Flag to print the move button or not
431 * @param boolean $printaddpage Flag to print the add page drop-down or not
432 * @param boolean $return Return flag
433 * @return mixed boolean/string
435 function lesson_print_page_actions($cmid, $page, $printmove, $printaddpage = false, $return = false) {
436 global $CFG;
438 $context = get_context_instance(CONTEXT_MODULE, $cmid);
439 $actions = array();
441 if (has_capability('mod/lesson:edit', $context)) {
442 if ($printmove) {
443 $actions[] = "<a title=\"".get_string('move')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=move&amp;pageid=$page->id\">
444 <img src=\"$CFG->pixpath/t/move.gif\" class=\"iconsmall\" alt=\"".get_string('move')."\" /></a>\n";
446 $actions[] = "<a title=\"".get_string('update')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=editpage&amp;pageid=$page->id\">
447 <img src=\"$CFG->pixpath/t/edit.gif\" class=\"iconsmall\" alt=\"".get_string('update')."\" /></a>\n";
449 $actions[] = "<a title=\"".get_string('preview')."\" href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&amp;pageid=$page->id\">
450 <img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"".get_string('preview')."\" /></a>\n";
452 $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\">
453 <img src=\"$CFG->pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"".get_string('delete')."\" /></a>\n";
455 if ($printaddpage) {
456 // Add page drop-down
457 $options = array();
458 $options['addcluster&amp;sesskey='.sesskey()] = get_string('clustertitle', 'lesson');
459 $options['addendofcluster&amp;sesskey='.sesskey()] = get_string('endofclustertitle', 'lesson');
460 $options['addbranchtable'] = get_string('branchtable', 'lesson');
461 $options['addendofbranch&amp;sesskey='.sesskey()] = get_string('endofbranch', 'lesson');
462 $options['addpage'] = get_string('question', 'lesson');
463 // Base url
464 $common = "$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;pageid=$page->id&amp;action=";
466 $actions[] = popup_form($common, $options, "addpage_$page->id", '', get_string('addpage', 'lesson').'...', '', '', true);
470 $actions = implode(' ', $actions);
472 if ($return) {
473 return $actions;
474 } else {
475 echo $actions;
476 return false;
481 * Prints the add links in expanded view or single view when editing
483 * @uses $CFG
484 * @param int $cmid Course Module ID
485 * @param int $prevpageid Previous page id
486 * @param boolean $return Return flag
487 * @return mixed boolean/string
488 * @todo &amp;pageid does not make sense, it is prevpageid
490 function lesson_print_add_links($cmid, $prevpageid, $return = false) {
491 global $CFG;
493 $context = get_context_instance(CONTEXT_MODULE, $cmid);
495 $links = '';
496 if (has_capability('mod/lesson:edit', $context)) {
497 $links = array();
498 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/import.php?id=$cmid&amp;pageid=$prevpageid\">".
499 get_string('importquestions', 'lesson').'</a>';
501 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addcluster&amp;pageid=$prevpageid\">".
502 get_string('addcluster', 'lesson').'</a>';
504 if ($prevpageid != 0) {
505 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofcluster&amp;pageid=$prevpageid\">".
506 get_string('addendofcluster', 'lesson').'</a>';
508 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addbranchtable&amp;pageid=$prevpageid\">".
509 get_string('addabranchtable', 'lesson').'</a>';
511 if ($prevpageid != 0) {
512 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofbranch&amp;pageid=$prevpageid\">".
513 get_string('addanendofbranch', 'lesson').'</a>';
516 $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addpage&amp;pageid=$prevpageid\">".
517 get_string('addaquestionpagehere', 'lesson').'</a>';
519 $links = implode(" | \n", $links);
520 $links = "\n<div class=\"addlinks\">\n$links\n</div>\n";
523 if ($return) {
524 return $links;
525 } else {
526 echo $links;
527 return true;
532 * Returns the string for a page type
534 * @uses $LESSON_QUESTION_TYPE
535 * @param int $qtype Page type
536 * @return string
538 function lesson_get_qtype_name($qtype) {
539 global $LESSON_QUESTION_TYPE;
540 switch ($qtype) {
541 case LESSON_ESSAY :
542 case LESSON_SHORTANSWER :
543 case LESSON_MULTICHOICE :
544 case LESSON_MATCHING :
545 case LESSON_TRUEFALSE :
546 case LESSON_NUMERICAL :
547 return $LESSON_QUESTION_TYPE[$qtype];
548 break;
549 case LESSON_BRANCHTABLE :
550 return get_string("branchtable", "lesson");
551 break;
552 case LESSON_ENDOFBRANCH :
553 return get_string("endofbranch", "lesson");
554 break;
555 case LESSON_CLUSTER :
556 return get_string("clustertitle", "lesson");
557 break;
558 case LESSON_ENDOFCLUSTER :
559 return get_string("endofclustertitle", "lesson");
560 break;
561 default:
562 return '';
563 break;
568 * Returns the string for a jump name
570 * @param int $jumpto Jump code or page ID
571 * @return string
573 function lesson_get_jump_name($jumpto) {
574 if ($jumpto == 0) {
575 $jumptitle = get_string('thispage', 'lesson');
576 } elseif ($jumpto == LESSON_NEXTPAGE) {
577 $jumptitle = get_string('nextpage', 'lesson');
578 } elseif ($jumpto == LESSON_EOL) {
579 $jumptitle = get_string('endoflesson', 'lesson');
580 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
581 $jumptitle = get_string('unseenpageinbranch', 'lesson');
582 } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
583 $jumptitle = get_string('previouspage', 'lesson');
584 } elseif ($jumpto == LESSON_RANDOMPAGE) {
585 $jumptitle = get_string('randompageinbranch', 'lesson');
586 } elseif ($jumpto == LESSON_RANDOMBRANCH) {
587 $jumptitle = get_string('randombranch', 'lesson');
588 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
589 $jumptitle = get_string('clusterjump', 'lesson');
590 } else {
591 if (!$jumptitle = get_field('lesson_pages', 'title', 'id', $jumpto)) {
592 $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
596 return format_string($jumptitle,true);
600 * Given some question info and some data about the the answers
601 * this function parses, organises and saves the question
603 * This is only used when IMPORTING questions and is only called
604 * from format.php
605 * Lifted from mod/quiz/lib.php -
606 * 1. all reference to oldanswers removed
607 * 2. all reference to quiz_multichoice table removed
608 * 3. In SHORTANSWER questions usecase is store in the qoption field
609 * 4. In NUMERIC questions store the range as two answers
610 * 5. TRUEFALSE options are ignored
611 * 6. For MULTICHOICE questions with more than one answer the qoption field is true
613 * @param opject $question Contains question data like question, type and answers.
614 * @return object Returns $result->error or $result->notice.
616 function lesson_save_question_options($question) {
618 $timenow = time();
619 switch ($question->qtype) {
620 case LESSON_SHORTANSWER:
622 $answers = array();
623 $maxfraction = -1;
625 // Insert all the new answers
626 foreach ($question->answer as $key => $dataanswer) {
627 if ($dataanswer != "") {
628 $answer = new stdClass;
629 $answer->lessonid = $question->lessonid;
630 $answer->pageid = $question->id;
631 if ($question->fraction[$key] >=0.5) {
632 $answer->jumpto = LESSON_NEXTPAGE;
634 $answer->timecreated = $timenow;
635 $answer->grade = $question->fraction[$key] * 100;
636 $answer->answer = $dataanswer;
637 $answer->response = $question->feedback[$key];
638 if (!$answer->id = insert_record("lesson_answers", $answer)) {
639 $result->error = "Could not insert shortanswer quiz answer!";
640 return $result;
642 $answers[] = $answer->id;
643 if ($question->fraction[$key] > $maxfraction) {
644 $maxfraction = $question->fraction[$key];
650 /// Perform sanity checks on fractional grades
651 if ($maxfraction != 1) {
652 $maxfraction = $maxfraction * 100;
653 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
654 return $result;
656 break;
658 case LESSON_NUMERICAL: // Note similarities to SHORTANSWER
660 $answers = array();
661 $maxfraction = -1;
664 // for each answer store the pair of min and max values even if they are the same
665 foreach ($question->answer as $key => $dataanswer) {
666 if ($dataanswer != "") {
667 $answer = new stdClass;
668 $answer->lessonid = $question->lessonid;
669 $answer->pageid = $question->id;
670 $answer->jumpto = LESSON_NEXTPAGE;
671 $answer->timecreated = $timenow;
672 $answer->grade = $question->fraction[$key] * 100;
673 $min = $question->answer[$key] - $question->tolerance[$key];
674 $max = $question->answer[$key] + $question->tolerance[$key];
675 $answer->answer = $min.":".$max;
676 // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max
677 $answer->response = $question->feedback[$key];
678 if (!$answer->id = insert_record("lesson_answers", $answer)) {
679 $result->error = "Could not insert numerical quiz answer!";
680 return $result;
683 $answers[] = $answer->id;
684 if ($question->fraction[$key] > $maxfraction) {
685 $maxfraction = $question->fraction[$key];
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;
696 break;
699 case LESSON_TRUEFALSE:
701 // the truth
702 $answer->lessonid = $question->lessonid;
703 $answer->pageid = $question->id;
704 $answer->timecreated = $timenow;
705 $answer->answer = get_string("true", "quiz");
706 $answer->grade = $question->answer * 100;
707 if ($answer->grade > 50 ) {
708 $answer->jumpto = LESSON_NEXTPAGE;
710 if (isset($question->feedbacktrue)) {
711 $answer->response = $question->feedbacktrue;
713 if (!$true->id = insert_record("lesson_answers", $answer)) {
714 $result->error = "Could not insert quiz answer \"true\")!";
715 return $result;
718 // the lie
719 $answer = new stdClass;
720 $answer->lessonid = $question->lessonid;
721 $answer->pageid = $question->id;
722 $answer->timecreated = $timenow;
723 $answer->answer = get_string("false", "quiz");
724 $answer->grade = (1 - (int)$question->answer) * 100;
725 if ($answer->grade > 50 ) {
726 $answer->jumpto = LESSON_NEXTPAGE;
728 if (isset($question->feedbackfalse)) {
729 $answer->response = $question->feedbackfalse;
731 if (!$false->id = insert_record("lesson_answers", $answer)) {
732 $result->error = "Could not insert quiz answer \"false\")!";
733 return $result;
736 break;
739 case LESSON_MULTICHOICE:
741 $totalfraction = 0;
742 $maxfraction = -1;
744 $answers = array();
746 // Insert all the new answers
747 foreach ($question->answer as $key => $dataanswer) {
748 if ($dataanswer != "") {
749 $answer = new stdClass;
750 $answer->lessonid = $question->lessonid;
751 $answer->pageid = $question->id;
752 $answer->timecreated = $timenow;
753 $answer->grade = $question->fraction[$key] * 100;
754 // changed some defaults
755 /* Original Code
756 if ($answer->grade > 50 ) {
757 $answer->jumpto = LESSON_NEXTPAGE;
759 Replaced with: */
760 if ($answer->grade > 50 ) {
761 $answer->jumpto = LESSON_NEXTPAGE;
762 $answer->score = 1;
764 // end Replace
765 $answer->answer = $dataanswer;
766 $answer->response = $question->feedback[$key];
767 if (!$answer->id = insert_record("lesson_answers", $answer)) {
768 $result->error = "Could not insert multichoice quiz answer! ";
769 return $result;
771 // for Sanity checks
772 if ($question->fraction[$key] > 0) {
773 $totalfraction += $question->fraction[$key];
775 if ($question->fraction[$key] > $maxfraction) {
776 $maxfraction = $question->fraction[$key];
781 /// Perform sanity checks on fractional grades
782 if ($question->single) {
783 if ($maxfraction != 1) {
784 $maxfraction = $maxfraction * 100;
785 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
786 return $result;
788 } else {
789 $totalfraction = round($totalfraction,2);
790 if ($totalfraction != 1) {
791 $totalfraction = $totalfraction * 100;
792 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
793 return $result;
796 break;
798 case LESSON_MATCHING:
800 $subquestions = array();
802 $i = 0;
803 // Insert all the new question+answer pairs
804 foreach ($question->subquestions as $key => $questiontext) {
805 $answertext = $question->subanswers[$key];
806 if (!empty($questiontext) and !empty($answertext)) {
807 $answer = new stdClass;
808 $answer->lessonid = $question->lessonid;
809 $answer->pageid = $question->id;
810 $answer->timecreated = $timenow;
811 $answer->answer = $questiontext;
812 $answer->response = $answertext;
813 if ($i == 0) {
814 // first answer contains the correct answer jump
815 $answer->jumpto = LESSON_NEXTPAGE;
817 if (!$subquestion->id = insert_record("lesson_answers", $answer)) {
818 $result->error = "Could not insert quiz match subquestion!";
819 return $result;
821 $subquestions[] = $subquestion->id;
822 $i++;
826 if (count($subquestions) < 3) {
827 $result->notice = get_string("notenoughsubquestions", "quiz");
828 return $result;
831 break;
834 case LESSON_RANDOMSAMATCH:
835 $options->question = $question->id;
836 $options->choose = $question->choose;
837 if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) {
838 $options->id = $existing->id;
839 if (!update_record("quiz_randomsamatch", $options)) {
840 $result->error = "Could not update quiz randomsamatch options!";
841 return $result;
843 } else {
844 if (!insert_record("quiz_randomsamatch", $options)) {
845 $result->error = "Could not insert quiz randomsamatch options!";
846 return $result;
849 break;
851 case LESSON_MULTIANSWER:
852 if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
853 $oldmultianswers = array();
856 // Insert all the new multi answers
857 foreach ($question->answers as $dataanswer) {
858 if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
859 $multianswer = $oldmultianswer;
860 $multianswer->positionkey = $dataanswer->positionkey;
861 $multianswer->norm = $dataanswer->norm;
862 $multianswer->answertype = $dataanswer->answertype;
864 if (! $multianswer->answers = quiz_save_multianswer_alternatives
865 ($question->id, $dataanswer->answertype,
866 $dataanswer->alternatives, $oldmultianswer->answers))
868 $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
869 return $result;
871 if (!update_record("quiz_multianswers", $multianswer)) {
872 $result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
873 return $result;
875 } else { // This is a completely new answer
876 $multianswer = new stdClass;
877 $multianswer->question = $question->id;
878 $multianswer->positionkey = $dataanswer->positionkey;
879 $multianswer->norm = $dataanswer->norm;
880 $multianswer->answertype = $dataanswer->answertype;
882 if (! $multianswer->answers = quiz_save_multianswer_alternatives
883 ($question->id, $dataanswer->answertype,
884 $dataanswer->alternatives))
886 $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
887 return $result;
889 if (!insert_record("quiz_multianswers", $multianswer)) {
890 $result->error = "Could not insert quiz multianswer!";
891 return $result;
895 break;
897 case LESSON_RANDOM:
898 break;
900 case LESSON_DESCRIPTION:
901 break;
903 default:
904 $result->error = "Unsupported question type ($question->qtype)!";
905 return $result;
906 break;
908 return true;
912 * Determins if a jumpto value is correct or not.
914 * returns true if jumpto page is (logically) after the pageid page or
915 * if the jumpto value is a special value. Returns false in all other cases.
917 * @param int $pageid Id of the page from which you are jumping from.
918 * @param int $jumpto The jumpto number.
919 * @return boolean True or false after a series of tests.
921 function lesson_iscorrect($pageid, $jumpto) {
923 // first test the special values
924 if (!$jumpto) {
925 // same page
926 return false;
927 } elseif ($jumpto == LESSON_NEXTPAGE) {
928 return true;
929 } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
930 return true;
931 } elseif ($jumpto == LESSON_RANDOMPAGE) {
932 return true;
933 } elseif ($jumpto == LESSON_CLUSTERJUMP) {
934 return true;
935 } elseif ($jumpto == LESSON_EOL) {
936 return true;
938 // we have to run through the pages from pageid looking for jumpid
939 if ($lessonid = get_field('lesson_pages', 'lessonid', 'id', $pageid)) {
940 if ($pages = get_records('lesson_pages', 'lessonid', $lessonid, '', 'id, nextpageid')) {
941 $apageid = $pages[$pageid]->nextpageid;
942 while ($apageid != 0) {
943 if ($jumpto == $apageid) {
944 return true;
946 $apageid = $pages[$apageid]->nextpageid;
950 return false;
954 * Checks to see if a page is a branch table or is
955 * a page that is enclosed by a branch table and an end of branch or end of lesson.
956 * May call this function: {@link lesson_is_page_in_branch()}
958 * @param int $lesson Id of the lesson to which the page belongs.
959 * @param int $pageid Id of the page.
960 * @return boolean True or false.
962 function lesson_display_branch_jumps($lessonid, $pageid) {
963 if($pageid == 0) {
964 // first page
965 return false;
967 // get all of the lesson pages
968 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
969 // adding first page
970 return false;
973 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
974 return true;
977 return lesson_is_page_in_branch($lessonpages, $pageid);
981 * Checks to see if a page is a cluster page or is
982 * a page that is enclosed by a cluster page and an end of cluster or end of lesson
983 * May call this function: {@link lesson_is_page_in_cluster()}
985 * @param int $lesson Id of the lesson to which the page belongs.
986 * @param int $pageid Id of the page.
987 * @return boolean True or false.
989 function lesson_display_cluster_jump($lesson, $pageid) {
990 if($pageid == 0) {
991 // first page
992 return false;
994 // get all of the lesson pages
995 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
996 // adding first page
997 return false;
1000 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1001 return true;
1004 return lesson_is_page_in_cluster($lessonpages, $pageid);
1009 * Checks to see if a LESSON_CLUSTERJUMP or
1010 * a LESSON_UNSEENBRANCHPAGE is used in a lesson.
1012 * This function is only executed when a teacher is
1013 * checking the navigation for a lesson.
1015 * @param int $lesson Id of the lesson that is to be checked.
1016 * @return boolean True or false.
1018 function lesson_display_teacher_warning($lesson) {
1019 // get all of the lesson answers
1020 if (!$lessonanswers = get_records_select("lesson_answers", "lessonid = $lesson")) {
1021 // no answers, then not useing cluster or unseen
1022 return false;
1024 // just check for the first one that fulfills the requirements
1025 foreach ($lessonanswers as $lessonanswer) {
1026 if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) {
1027 return true;
1031 // if no answers use either of the two jumps
1032 return false;
1037 * Interprets LESSON_CLUSTERJUMP jumpto value.
1039 * This will select a page randomly
1040 * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
1041 * and the page selected will be a page that has not been viewed already
1042 * and if any pages are within a branch table or end of branch then only 1 page within
1043 * the branch table or end of branch will be randomly selected (sub clustering).
1045 * @param int $lessonid Id of the lesson.
1046 * @param int $userid Id of the user.
1047 * @param int $pageid Id of the current page from which we are jumping from.
1048 * @return int The id of the next page.
1050 function lesson_cluster_jump($lessonid, $userid, $pageid) {
1051 // get the number of retakes
1052 if (!$retakes = count_records("lesson_grades", "lessonid", $lessonid, "userid", $userid)) {
1053 $retakes = 0;
1056 // get all the lesson_attempts aka what the user has seen
1057 if ($seen = get_records_select("lesson_attempts", "lessonid = $lessonid AND userid = $userid AND retry = $retakes", "timeseen DESC")) {
1058 foreach ($seen as $value) { // load it into an array that I can more easily use
1059 $seenpages[$value->pageid] = $value->pageid;
1061 } else {
1062 $seenpages = array();
1065 // get the lesson pages
1066 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1067 error("Error: could not find records in lesson_pages table");
1069 // find the start of the cluster
1070 while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
1071 if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1072 break;
1074 $pageid = $lessonpages[$pageid]->prevpageid;
1077 $pageid = $lessonpages[$pageid]->nextpageid; // move down from the cluster page
1079 $clusterpages = array();
1080 while (true) { // now load all the pages into the cluster that are not already inside of a branch table.
1081 if ($lessonpages[$pageid]->qtype == LESSON_ENDOFCLUSTER) {
1082 // store the endofcluster page's jump
1083 $exitjump = get_field("lesson_answers", "jumpto", "pageid", $pageid, "lessonid", $lessonid);
1084 if ($exitjump == LESSON_NEXTPAGE) {
1085 $exitjump = $lessonpages[$pageid]->nextpageid;
1087 if ($exitjump == 0) {
1088 $exitjump = LESSON_EOL;
1090 break;
1091 } elseif (!lesson_is_page_in_branch($lessonpages, $pageid) && $lessonpages[$pageid]->qtype != LESSON_ENDOFBRANCH) {
1092 // load page into array when it is not in a branch table and when it is not an endofbranch
1093 $clusterpages[] = $lessonpages[$pageid];
1095 if ($lessonpages[$pageid]->nextpageid == 0) {
1096 // shouldn't ever get here... should be using endofcluster
1097 $exitjump = LESSON_EOL;
1098 break;
1099 } else {
1100 $pageid = $lessonpages[$pageid]->nextpageid;
1104 // filter out the ones we have seen
1105 $unseen = array();
1106 foreach ($clusterpages as $clusterpage) {
1107 if ($clusterpage->qtype == LESSON_BRANCHTABLE) { // if branchtable, check to see if any pages inside have been viewed
1108 $branchpages = lesson_pages_in_branch($lessonpages, $clusterpage->id); // get the pages in the branchtable
1109 $flag = true;
1110 foreach ($branchpages as $branchpage) {
1111 if (array_key_exists($branchpage->id, $seenpages)) { // check if any of the pages have been viewed
1112 $flag = false;
1115 if ($flag && count($branchpages) > 0) {
1116 // add branch table
1117 $unseen[] = $clusterpage;
1119 } else {
1120 // add any other type of page that has not already been viewed
1121 if (!array_key_exists($clusterpage->id, $seenpages)) {
1122 $unseen[] = $clusterpage;
1127 if (count($unseen) > 0) { // it does not contain elements, then use exitjump, otherwise find out next page/branch
1128 $nextpage = $unseen[rand(0, count($unseen)-1)];
1129 } else {
1130 return $exitjump; // seen all there is to see, leave the cluster
1133 if ($nextpage->qtype == LESSON_BRANCHTABLE) { // if branch table, then pick a random page inside of it
1134 $branchpages = lesson_pages_in_branch($lessonpages, $nextpage->id);
1135 return $branchpages[rand(0, count($branchpages)-1)]->id;
1136 } else { // otherwise, return the page's id
1137 return $nextpage->id;
1142 * Returns pages that are within a branch table and another branch table, end of branch or end of lesson
1144 * @param array $lessonpages An array of lesson page objects.
1145 * @param int $branchid The id of the branch table that we would like the containing pages for.
1146 * @return array An array of lesson page objects.
1148 function lesson_pages_in_branch($lessonpages, $branchid) {
1149 $pageid = $lessonpages[$branchid]->nextpageid; // move to the first page after the branch table
1150 $pagesinbranch = array();
1152 while (true) {
1153 if ($pageid == 0) { // EOL
1154 break;
1155 } elseif ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1156 break;
1157 } elseif ($lessonpages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1158 break;
1160 $pagesinbranch[] = $lessonpages[$pageid];
1161 $pageid = $lessonpages[$pageid]->nextpageid;
1164 return $pagesinbranch;
1168 * Interprets the LESSON_UNSEENBRANCHPAGE jump.
1170 * will return the pageid of a random unseen page that is within a branch
1172 * @see lesson_pages_in_branch()
1173 * @param int $lesson Id of the lesson.
1174 * @param int $userid Id of the user.
1175 * @param int $pageid Id of the page from which we are jumping.
1176 * @return int Id of the next page.
1178 function lesson_unseen_question_jump($lesson, $user, $pageid) {
1179 // get the number of retakes
1180 if (!$retakes = count_records("lesson_grades", "lessonid", $lesson, "userid", $user)) {
1181 $retakes = 0;
1184 // get all the lesson_attempts aka what the user has seen
1185 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson AND userid = $user AND retry = $retakes", "timeseen DESC")) {
1186 foreach($viewedpages as $viewed) {
1187 $seenpages[] = $viewed->pageid;
1189 } else {
1190 $seenpages = array();
1193 // get the lesson pages
1194 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lesson")) {
1195 error("Error: could not find records in lesson_pages table");
1198 if ($pageid == LESSON_UNSEENBRANCHPAGE) { // this only happens when a student leaves in the middle of an unseen question within a branch series
1199 $pageid = $seenpages[0]; // just change the pageid to the last page viewed inside the branch table
1202 // go up the pages till branch table
1203 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1204 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1205 break;
1207 $pageid = $lessonpages[$pageid]->prevpageid;
1210 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1212 // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array
1213 $unseen = array();
1214 foreach($pagesinbranch as $page) {
1215 if (!in_array($page->id, $seenpages)) {
1216 $unseen[] = $page->id;
1220 if(count($unseen) == 0) {
1221 if(isset($pagesinbranch)) {
1222 $temp = end($pagesinbranch);
1223 $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL
1224 } else {
1225 // there are no pages inside the branch, so return the next page
1226 $nextpage = $lessonpages[$pageid]->nextpageid;
1228 if ($nextpage == 0) {
1229 return LESSON_EOL;
1230 } else {
1231 return $nextpage;
1233 } else {
1234 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1239 * Handles the unseen branch table jump.
1241 * @param int $lessonid Lesson id.
1242 * @param int $userid User id.
1243 * @return int Will return the page id of a branch table or end of lesson
1245 function lesson_unseen_branch_jump($lessonid, $userid) {
1246 if (!$retakes = count_records("lesson_grades", "lessonid", $lessonid, "userid", $userid)) {
1247 $retakes = 0;
1250 if (!$seenbranches = get_records_select("lesson_branch", "lessonid = $lessonid AND userid = $userid AND retry = $retakes",
1251 "timeseen DESC")) {
1252 error("Error: could not find records in lesson_branch table");
1255 // get the lesson pages
1256 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1257 error("Error: could not find records in lesson_pages table");
1260 // this loads all the viewed branch tables into $seen untill it finds the branch table with the flag
1261 // which is the branch table that starts the unseenbranch function
1262 $seen = array();
1263 foreach ($seenbranches as $seenbranch) {
1264 if (!$seenbranch->flag) {
1265 $seen[$seenbranch->pageid] = $seenbranch->pageid;
1266 } else {
1267 $start = $seenbranch->pageid;
1268 break;
1271 // this function searches through the lesson pages to find all the branch tables
1272 // that follow the flagged branch table
1273 $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table
1274 while ($pageid != 0) { // grab all of the branch table till eol
1275 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1276 $branchtables[] = $lessonpages[$pageid]->id;
1278 $pageid = $lessonpages[$pageid]->nextpageid;
1280 $unseen = array();
1281 foreach ($branchtables as $branchtable) {
1282 // load all of the unseen branch tables into unseen
1283 if (!array_key_exists($branchtable, $seen)) {
1284 $unseen[] = $branchtable;
1287 if (count($unseen) > 0) {
1288 return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page
1289 } else {
1290 return LESSON_EOL; // has viewed all of the branch tables
1295 * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE).
1297 * @param int $lessonid Lesson id.
1298 * @param int $pageid The id of the page that we are jumping from (?)
1299 * @return int The pageid of a random page that is within a branch table
1301 function lesson_random_question_jump($lessonid, $pageid) {
1302 // get the lesson pages
1303 if (!$lessonpages = get_records_select("lesson_pages", "lessonid = $lessonid")) {
1304 error("Error: could not find records in lesson_pages table");
1307 // go up the pages till branch table
1308 while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1310 if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1311 break;
1313 $pageid = $lessonpages[$pageid]->prevpageid;
1316 // get the pages within the branch
1317 $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1319 if(count($pagesinbranch) == 0) {
1320 // there are no pages inside the branch, so return the next page
1321 return $lessonpages[$pageid]->nextpageid;
1322 } else {
1323 return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id; // returns a random page id for the next page
1328 * Check to see if a page is below a branch table (logically).
1330 * Will return true if a branch table is found logically above the page.
1331 * Will return false if an end of branch, cluster or the beginning
1332 * of the lesson is found before a branch table.
1334 * @param array $pages An array of lesson page objects.
1335 * @param int $pageid Id of the page for testing.
1336 * @return boolean
1338 function lesson_is_page_in_branch($pages, $pageid) {
1339 $pageid = $pages[$pageid]->prevpageid; // move up one
1341 // go up the pages till branch table
1342 while (true) {
1343 if ($pageid == 0) { // ran into the beginning of the lesson
1344 return false;
1345 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFBRANCH) { // ran into the end of another branch table
1346 return false;
1347 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // do not look beyond a cluster
1348 return false;
1349 } elseif ($pages[$pageid]->qtype == LESSON_BRANCHTABLE) { // hit a branch table
1350 return true;
1352 $pageid = $pages[$pageid]->prevpageid;
1358 * Check to see if a page is below a cluster page (logically).
1360 * Will return true if a cluster is found logically above the page.
1361 * Will return false if an end of cluster or the beginning
1362 * of the lesson is found before a cluster page.
1364 * @param array $pages An array of lesson page objects.
1365 * @param int $pageid Id of the page for testing.
1366 * @return boolean
1368 function lesson_is_page_in_cluster($pages, $pageid) {
1369 $pageid = $pages[$pageid]->prevpageid; // move up one
1371 // go up the pages till branch table
1372 while (true) {
1373 if ($pageid == 0) { // ran into the beginning of the lesson
1374 return false;
1375 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER) { // ran into the end of another branch table
1376 return false;
1377 } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // hit a branch table
1378 return true;
1380 $pageid = $pages[$pageid]->prevpageid;
1385 * Calculates a user's grade for a lesson.
1387 * @param object $lesson The lesson that the user is taking.
1388 * @param int $retries The attempt number.
1389 * @param int $userid Id of the user (optinal, default current user).
1390 * @return object { nquestions => number of questions answered
1391 attempts => number of question attempts
1392 total => max points possible
1393 earned => points earned by student
1394 grade => calculated percentage grade
1395 nmanual => number of manually graded questions
1396 manualpoints => point value for manually graded questions }
1398 function lesson_grade($lesson, $ntries, $userid = 0) {
1399 global $USER;
1401 if (empty($userid)) {
1402 $userid = $USER->id;
1405 // Zero out everything
1406 $ncorrect = 0;
1407 $nviewed = 0;
1408 $score = 0;
1409 $nmanual = 0;
1410 $manualpoints = 0;
1411 $thegrade = 0;
1412 $nquestions = 0;
1413 $total = 0;
1414 $earned = 0;
1416 if ($useranswers = get_records_select("lesson_attempts", "lessonid = $lesson->id AND
1417 userid = $userid AND retry = $ntries", "timeseen")) {
1418 // group each try with its page
1419 $attemptset = array();
1420 foreach ($useranswers as $useranswer) {
1421 $attemptset[$useranswer->pageid][] = $useranswer;
1424 // Drop all attempts that go beyond max attempts for the lesson
1425 foreach ($attemptset as $key => $set) {
1426 $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts);
1429 $pageids = implode(",", array_keys($attemptset));
1431 // get only the pages and their answers that the user answered
1432 $pages = get_records_select("lesson_pages", "lessonid = $lesson->id AND id IN($pageids)");
1433 $answers = get_records_select("lesson_answers", "lessonid = $lesson->id AND pageid IN($pageids)");
1435 // Number of pages answered
1436 $nquestions = count($pages);
1438 foreach ($attemptset as $attempts) {
1439 if ($lesson->custom) {
1440 $attempt = end($attempts);
1441 // If essay question, handle it, otherwise add to score
1442 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1443 $essayinfo = unserialize($attempt->useranswer);
1444 $earned += $essayinfo->score;
1445 $nmanual++;
1446 $manualpoints += $answers[$attempt->answerid]->score;
1447 } else {
1448 $earned += $answers[$attempt->answerid]->score;
1450 } else {
1451 foreach ($attempts as $attempt) {
1452 $earned += $attempt->correct;
1454 $attempt = end($attempts); // doesn't matter which one
1455 // If essay question, increase numbers
1456 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1457 $nmanual++;
1458 $manualpoints++;
1461 // Number of times answered
1462 $nviewed += count($attempts);
1465 if ($lesson->custom) {
1466 $bestscores = array();
1467 // Find the highest possible score per page to get our total
1468 foreach ($answers as $answer) {
1469 if(!isset($bestscores[$answer->pageid])) {
1470 $bestscores[$answer->pageid] = $answer->score;
1471 } else if ($bestscores[$answer->pageid] < $answer->score) {
1472 $bestscores[$answer->pageid] = $answer->score;
1475 $total = array_sum($bestscores);
1476 } else {
1477 // Check to make sure the student has answered the minimum questions
1478 if ($lesson->minquestions and $nquestions < $lesson->minquestions) {
1479 // Nope, increase number viewed by the amount of unanswered questions
1480 $total = $nviewed + ($lesson->minquestions - $nquestions);
1481 } else {
1482 $total = $nviewed;
1487 if ($total) { // not zero
1488 $thegrade = round(100 * $earned / $total, 5);
1491 // Build the grade information object
1492 $gradeinfo = new stdClass;
1493 $gradeinfo->nquestions = $nquestions;
1494 $gradeinfo->attempts = $nviewed;
1495 $gradeinfo->total = $total;
1496 $gradeinfo->earned = $earned;
1497 $gradeinfo->grade = $thegrade;
1498 $gradeinfo->nmanual = $nmanual;
1499 $gradeinfo->manualpoints = $manualpoints;
1501 return $gradeinfo;
1505 * Prints the on going message to the user.
1507 * With custom grading On, displays points
1508 * earned out of total points possible thus far.
1509 * With custom grading Off, displays number of correct
1510 * answers out of total attempted.
1512 * @param object $lesson The lesson that the user is taking.
1513 * @return void
1515 function lesson_print_ongoing_score($lesson) {
1516 global $USER;
1517 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1518 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1520 if (has_capability('mod/lesson:manage', $context)) {
1521 echo "<p align=\"center\">".get_string('teacherongoingwarning', 'lesson').'</p>';
1522 } else {
1523 $ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id);
1524 if (isset($USER->modattempts[$lesson->id])) {
1525 $ntries--;
1527 $gradeinfo = lesson_grade($lesson, $ntries);
1529 $a = new stdClass;
1530 if ($lesson->custom) {
1531 $a->score = $gradeinfo->earned;
1532 $a->currenthigh = $gradeinfo->total;
1533 print_simple_box(get_string("ongoingcustom", "lesson", $a), "center");
1534 } else {
1535 $a->correct = $gradeinfo->earned;
1536 $a->viewed = $gradeinfo->attempts;
1537 print_simple_box(get_string("ongoingnormal", "lesson", $a), "center");
1543 * Prints tabs for the editing and adding pages. Each tab is a question type.
1545 * @param array $qtypes The question types array (may not need to pass this because it is defined in this file)
1546 * @param string $selected Current selected tab
1547 * @param string $link The base href value of the link for the tab
1548 * @param string $onclick Javascript for the tab link
1549 * @return void
1551 function lesson_qtype_menu($qtypes, $selected="", $link="", $onclick="") {
1552 $tabs = array();
1553 $tabrows = array();
1555 foreach ($qtypes as $qtype => $qtypename) {
1556 $tabrows[] = new tabobject($qtype, "$link&amp;qtype=$qtype\" onclick=\"$onclick", $qtypename);
1558 $tabs[] = $tabrows;
1559 print_tabs($tabs, $selected);
1560 echo "<input type=\"hidden\" name=\"qtype\" value=\"$selected\" /> \n";
1565 * Prints out a Progress Bar which depicts a user's progress within a lesson.
1567 * Currently works best with a linear lesson. Clusters are counted as a single page.
1568 * Also, only viewed branch tables and questions that have been answered correctly count
1569 * toward lesson completion (or progress). Only Students can see the Progress bar as well.
1571 * @param object $lesson The lesson that the user is currently taking.
1572 * @param object $course The course that the to which the lesson belongs.
1573 * @return boolean The return is not significant as of yet. Will return true/false.
1575 function lesson_print_progress_bar($lesson, $course) {
1576 global $CFG, $USER;
1577 $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1578 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1580 // lesson setting to turn progress bar on or off
1581 if (!$lesson->progressbar) {
1582 return false;
1585 // catch teachers
1586 if (has_capability('mod/lesson:manage', $context)) {
1587 notify(get_string('progressbarteacherwarning', 'lesson', $course->teachers));
1588 return false;
1590 if (!isset($USER->modattempts[$lesson->id])) {
1591 // all of the lesson pages
1592 if (!$pages = get_records('lesson_pages', 'lessonid', $lesson->id)) {
1593 return false;
1594 } else {
1595 foreach ($pages as $page) {
1596 if ($page->prevpageid == 0) {
1597 $pageid = $page->id; // find the first page id
1598 break;
1603 // current attempt number
1604 if (!$ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id)) {
1605 $ntries = 0; // may not be necessary
1608 $viewedpageids = array();
1610 // collect all of the correctly answered questions
1611 if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries AND correct = 1", 'timeseen DESC', 'pageid, id')) {
1612 $viewedpageids = array_keys($viewedpages);
1614 // collect all of the branch tables viewed
1615 if ($viewedbranches = get_records_select("lesson_branch", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries", 'timeseen DESC', 'pageid, id')) {
1616 $viewedpageids = array_merge($viewedpageids, array_keys($viewedbranches));
1619 // Filter out the following pages:
1620 // End of Cluster
1621 // End of Branch
1622 // Pages found inside of Clusters
1623 // Do not filter out Cluster Page(s) because we count a cluster as one.
1624 // By keeping the cluster page, we get our 1
1625 $validpages = array();
1626 while ($pageid != 0) {
1627 if ($pages[$pageid]->qtype == LESSON_CLUSTER) {
1628 $clusterpageid = $pageid; // copy it
1629 $validpages[$clusterpageid] = 1; // add the cluster page as a valid page
1630 $pageid = $pages[$pageid]->nextpageid; // get next page
1632 // now, remove all necessary viewed paged ids from the viewedpageids array.
1633 while ($pages[$pageid]->qtype != LESSON_ENDOFCLUSTER and $pageid != 0) {
1634 if (in_array($pageid, $viewedpageids)) {
1635 unset($viewedpageids[array_search($pageid, $viewedpageids)]); // remove it
1636 // since the user did see one page in the cluster, add the cluster pageid to the viewedpageids
1637 if (!in_array($clusterpageid, $viewedpageids)) {
1638 $viewedpageids[] = $clusterpageid;
1641 $pageid = $pages[$pageid]->nextpageid;
1643 } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER or $pages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1644 // dont count these, just go to next
1645 $pageid = $pages[$pageid]->nextpageid;
1646 } else {
1647 // a counted page
1648 $validpages[$pageid] = 1;
1649 $pageid = $pages[$pageid]->nextpageid;
1653 // progress calculation as a percent
1654 $progress = round(count($viewedpageids)/count($validpages), 2) * 100;
1655 } else {
1656 $progress = 100;
1659 // print out the Progress Bar. Attempted to put as much as possible in the style sheets.
1660 echo '<div class="progress_bar" align="center">';
1661 echo '<table class="progress_bar_table"><tr>';
1662 if ($progress != 0) { // some browsers do not repsect the 0 width.
1663 echo '<td style="width:'.$progress.'%;" class="progress_bar_completed">';
1664 echo '</td>';
1666 echo '<td class="progress_bar_todo">';
1667 echo '<div class="progress_bar_token"></div>';
1668 echo '</td>';
1669 echo '</tr></table>';
1670 echo '</div>';
1672 return true;
1676 * Determines if a user can view the left menu. The determining factor
1677 * is whether a user has a grade greater than or equal to the lesson setting
1678 * of displayleftif
1680 * @param object $lesson Lesson object of the current lesson
1681 * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged
1683 function lesson_displayleftif($lesson) {
1684 global $CFG, $USER;
1686 if (!empty($lesson->displayleftif)) {
1687 // get the current user's max grade for this lesson
1688 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')) {
1689 if ($maxgrade->maxgrade < $lesson->displayleftif) {
1690 return 0; // turn off the displayleft
1692 } else {
1693 return 0; // no grades
1697 // if we get to here, keep the original state of displayleft lesson setting
1698 return $lesson->displayleft;
1702 * If there is a media file associated with this
1703 * lesson, then print it in a block.
1705 * @param int $cmid Course Module ID for this lesson
1706 * @param object $lesson Full lesson record object
1707 * @return void
1709 function lesson_print_mediafile_block($cmid, $lesson) {
1710 if (!empty($lesson->mediafile)) {
1711 $url = '/mod/lesson/mediafile.php?id='.$cmid;
1712 $options = 'menubar=0,location=0,left=5,top=5,scrollbars,resizable,width='. $lesson->mediawidth .',height='. $lesson->mediaheight;
1713 $name = 'lessonmediafile';
1715 $content = link_to_popup_window ($url, $name, get_string('mediafilepopup', 'lesson'), '', '', get_string('mediafilepopup', 'lesson'), $options, true);
1716 $content .= helpbutton("mediafilestudent", get_string("mediafile", "lesson"), "lesson", true, false, '', true);
1718 print_side_block(get_string('linkedmedia', 'lesson'), $content, NULL, NULL, '', array('class' => 'mediafile'), get_string('linkedmedia', 'lesson'));
1723 * If a timed lesson and not a teacher, then
1724 * print the clock
1726 * @param int $cmid Course Module ID for this lesson
1727 * @param object $lesson Full lesson record object
1728 * @param object $timer Full timer record object
1729 * @return void
1731 function lesson_print_clock_block($cmid, $lesson, $timer) {
1732 global $CFG;
1734 $context = get_context_instance(CONTEXT_MODULE, $cmid);
1736 // Display for timed lessons and for students only
1737 if($lesson->timed and !has_capability('mod/lesson:manage', $context) and !empty($timer)) {
1738 $content = '<script type="text/javascript" charset="utf-8">'."\n";
1739 $content .= "<!--\n";
1740 $content .= ' var starttime = '.$timer->starttime.";\n";
1741 $content .= ' var servertime = '.time().";\n";
1742 $content .= ' var testlength = '.($lesson->maxtime * 60).";\n";
1743 $content .= ' document.write(\'<script type="text/javascript" src="'.$CFG->wwwroot.'/mod/lesson/timer.js" charset="utf-8"><\/script>\');'."\n";
1744 $content .= " window.onload = function () { show_clock(); };\n";
1745 $content .= "// -->\n";
1746 $content .= "</script>\n";
1747 $content .= "<noscript>\n";
1748 $content .= lesson_print_time_remaining($timer->starttime, $lesson->maxtime, true)."\n";
1749 $content .= "</noscript>\n";
1751 print_side_block(get_string('timeremaining', 'lesson'), $content, NULL, NULL, '', array('class' => 'clock'), get_string('timeremaining', 'lesson'));
1756 * If left menu is turned on, then this will
1757 * print the menu in a block
1759 * @param int $cmid Course Module ID for this lesson
1760 * @param object $lesson Full lesson record object
1761 * @return void
1763 function lesson_print_menu_block($cmid, $lesson) {
1764 global $CFG;
1766 if ($lesson->displayleft) {
1767 $pageid = get_field('lesson_pages', 'id', 'lessonid', $lesson->id, 'prevpageid', 0);
1768 $pages = get_records_select('lesson_pages', "lessonid = $lesson->id");
1769 $currentpageid = optional_param('pageid', $pageid, PARAM_INT);
1771 if ($pageid and $pages) {
1772 $content = '<a href="#maincontent" class="skip">'.get_string('skip', 'lesson')."</a>\n<div class=\"menuwrapper\">\n<ul>\n";
1774 while ($pageid != 0) {
1775 $page = $pages[$pageid];
1777 // Only process branch tables with display turned on
1778 if ($page->qtype == LESSON_BRANCHTABLE and $page->display) {
1779 if ($page->id == $currentpageid) {
1780 $content .= '<li class="selected">'.format_string($page->title,true)."</a></li>\n";
1781 } else {
1782 $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";
1786 $pageid = $page->nextpageid;
1788 $content .= "</ul>\n</div>\n";
1789 print_side_block(get_string('lessonmenu', 'lesson'), $content, NULL, NULL, '', array('class' => 'menu'), get_string('lessonmenu', 'lesson'));
1795 * This is not ideal, but checks to see if a
1796 * column has "block" content.
1798 * In the future, it would be nice if the lesson
1799 * media file, timer and navigation were blocks
1800 * then this would be unnecessary.
1802 * @uses $CFG
1803 * @uses $PAGE
1804 * @param object $lesson Full lesson record object
1805 * @param array $pageblocks An array of block instances organized by left and right columns
1806 * @param string $column Pass either BLOCK_POS_RIGHT or BLOCK_POS_LEFT constants
1807 * @return boolean
1809 function lesson_blocks_have_content($lesson, $pageblocks, $column) {
1810 global $CFG, $PAGE;
1812 // First check lesson conditions
1813 if ($column == BLOCK_POS_RIGHT) {
1814 $managecap = false;
1815 if ($cm = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)) {
1816 $managecap = has_capability('mod/lesson:manage', get_context_instance(CONTEXT_MODULE, $cm->id));
1818 if (($lesson->timed and !$managecap) or !empty($lesson->mediafile)) {
1819 return true;
1821 } else if ($column == BLOCK_POS_LEFT) {
1822 if ($lesson->displayleft) {
1823 return true;
1826 if (!empty($CFG->showblocksonmodpages)) {
1827 if ((blocks_have_content($pageblocks, $column) || $PAGE->user_is_editing())) {
1828 return true;
1832 return false;