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