lesson MDL-19812 Updated print_header and build_navigation to OUTPUT and PAGE equivalents
[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     $PAGE->set_title($title);
264     $PAGE->set_heading($course->fullname);
265     $PAGE->set_button($buttons);
266     echo $OUTPUT->header();
268     if (has_capability('mod/lesson:manage', $context)) {
269         print_heading_with_help($activityname, 'overview', 'lesson');
271         if (!empty($currenttab)) {
272             include($CFG->dirroot.'/mod/lesson/tabs.php');
273         }
274     } else {
275         echo $OUTPUT->heading($activityname);
276     }
278     lesson_print_messages();
281 /**
282  * Returns course module, course and module instance given 
283  * either the course module ID or a lesson module ID.
284  *
285  * @param int $cmid Course Module ID
286  * @param int $lessonid Lesson module instance ID
287  * @return array array($cm, $course, $lesson)
288  **/
289 function lesson_get_basics($cmid = 0, $lessonid = 0) {
290     global $DB;
291     
292     if ($cmid) {
293         if (!$cm = get_coursemodule_from_id('lesson', $cmid)) {
294             print_error('invalidcoursemodule');
295         }
296         if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
297             print_error('coursemisconf');
298         }
299         if (!$lesson = $DB->get_record('lesson', array('id' => $cm->instance))) {
300             print_error('invalidcoursemodule');
301         }
302     } else if ($lessonid) {
303         if (!$lesson = $DB->get_record('lesson', array('id' => $lessonid))) {
304             print_error('invalidcoursemodule');
305         }
306         if (!$course = $DB->get_record('course', array('id' => $lesson->course))) {
307             print_error('coursemisconf');
308         }
309         if (!$cm = get_coursemodule_from_instance('lesson', $lesson->id, $course->id)) {
310             print_error('invalidcoursemodule');
311         }
312     } else {
313         print_error('invalidid', 'lesson');
314     }
315     
316     return array($cm, $course, $lesson);
319 /**
320  * Sets a message to be printed.  Messages are printed
321  * by calling {@link lesson_print_messages()}.
322  *
323  * @uses $SESSION
324  * @param string $message The message to be printed
325  * @param string $class Class to be passed to {@link notify()}.  Usually notifyproblem or notifysuccess.
326  * @param string $align Alignment of the message
327  * @return boolean
328  **/
329 function lesson_set_message($message, $class="notifyproblem", $align='center') {
330     global $SESSION;
331     
332     if (empty($SESSION->lesson_messages) or !is_array($SESSION->lesson_messages)) {
333         $SESSION->lesson_messages = array();
334     }
335     
336     $SESSION->lesson_messages[] = array($message, $class, $align);
337     
338     return true;
341 /**
342  * Print all set messages.
343  *
344  * See {@link lesson_set_message()} for setting messages.
345  *
346  * Uses {@link notify()} to print the messages.
347  *
348  * @uses $SESSION
349  * @return boolean
350  **/
351 function lesson_print_messages() {
352     global $SESSION, $OUTPUT;
353     
354     if (empty($SESSION->lesson_messages)) {
355         // No messages to print
356         return true;
357     }
358     
359     foreach($SESSION->lesson_messages as $message) {
360         echo $OUTPUT->notification($message[0], $message[1], $message[2]);
361     }
362     
363     // Reset
364     unset($SESSION->lesson_messages);
365     
366     return true;
369 /**
370  * Prints a lesson link that submits a form.
371  *
372  * If Javascript is disabled, then a regular submit button is printed
373  *
374  * @param string $name Name of the link or button
375  * @param string $form The name of the form to be submitted
376  * @param string $align Alignment of the button
377  * @param string $class Class names to add to the div wrapper
378  * @param string $title Title for the link (Not used if javascript is disabled)
379  * @param string $id ID tag
380  * @param boolean $return Return flag
381  * @return mixed boolean/html
382  **/
383 function lesson_print_submit_link($name, $form, $align = 'center', $class='standardbutton', $title = '', $id = '', $return = false) {
384     if (!empty($align)) {
385         $align = " style=\"text-align:$align\"";
386     }
387     if (!empty($id)) {
388         $id = " id=\"$id\"";
389     }
390     if (empty($title)) {
391         $title = $name;
392     }
394     $output = "<div class=\"lessonbutton $class\" $align>\n";
395     $output .= "<input type=\"submit\" value=\"$name\" $align $id />";
396     $output .= "</div>\n";
397     
398     if ($return) {
399         return $output;
400     } else {
401         echo $output;
402         return true;
403     }
406 /**
407  * Prints a time remaining in the following format: H:MM:SS
408  *
409  * @param int $starttime Time when the lesson started
410  * @param int $maxtime Length of the lesson
411  * @param boolean $return Return output switch
412  * @return mixed boolean/string
413  **/
414 function lesson_print_time_remaining($starttime, $maxtime, $return = false) {
415     // Calculate hours, minutes and seconds
416     $timeleft = $starttime + $maxtime * 60 - time();
417     $hours = floor($timeleft/3600);
418     $timeleft = $timeleft - ($hours * 3600);
419     $minutes = floor($timeleft/60);
420     $secs = $timeleft - ($minutes * 60);
421     
422     if ($minutes < 10) {
423         $minutes = "0$minutes";
424     }
425     if ($secs < 10) {
426         $secs = "0$secs";
427     }
428     $output   = array();
429     $output[] = $hours;
430     $output[] = $minutes;
431     $output[] = $secs;
432     
433     $output = implode(':', $output);
434     
435     if ($return) {
436         return $output;
437     } else {
438         echo $output;
439         return true;
440     }
443 /**
444  * Prints the page action buttons
445  *
446  * Move/Edit/Preview/Delete
447  *
448  * @uses $CFG
449  * @param int $cmid Course Module ID
450  * @param object $page Page record
451  * @param boolean $printmove Flag to print the move button or not
452  * @param boolean $printaddpage Flag to print the add page drop-down or not
453  * @param boolean $return Return flag
454  * @return mixed boolean/string
455  **/
456 function lesson_print_page_actions($cmid, $page, $printmove, $printaddpage = false, $return = false) {
457     global $CFG, $OUTPUT;
459     $context = get_context_instance(CONTEXT_MODULE, $cmid);
460     $actions = array();
462     if (has_capability('mod/lesson:edit', $context)) {
463         if ($printmove) {
464             $actions[] = "<a title=\"".get_string('move')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=move&amp;pageid=$page->id\">
465                           <img src=\"" . $OUTPUT->old_icon_url('t/move') . "\" class=\"iconsmall\" alt=\"".get_string('move')."\" /></a>\n";
466         }
467         $actions[] = "<a title=\"".get_string('update')."\" href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=editpage&amp;pageid=$page->id\">
468                       <img src=\"" . $OUTPUT->old_icon_url('t/edit') . "\" class=\"iconsmall\" alt=\"".get_string('update')."\" /></a>\n";
469         
470         $actions[] = "<a title=\"".get_string('preview')."\" href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&amp;pageid=$page->id\">
471                       <img src=\"" . $OUTPUT->old_icon_url('t/preview') . "\" class=\"iconsmall\" alt=\"".get_string('preview')."\" /></a>\n";
473         $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\">
474                       <img src=\"" . $OUTPUT->old_icon_url('t/delete') . "\" class=\"iconsmall\" alt=\"".get_string('delete')."\" /></a>\n";
476         if ($printaddpage) {
477             // Add page drop-down
478             $options = array();
479             $options['addcluster&amp;sesskey='.sesskey()]      = get_string('clustertitle', 'lesson');
480             $options['addendofcluster&amp;sesskey='.sesskey()] = get_string('endofclustertitle', 'lesson');
481             $options['addbranchtable']                         = get_string('branchtable', 'lesson');
482             $options['addendofbranch&amp;sesskey='.sesskey()]  = get_string('endofbranch', 'lesson');
483             $options['addpage']                                = get_string('question', 'lesson');
484             // Base url
485             $common = "$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&pageid=$page->id";
486             $select = html_select::make_popup_form($common, 'action', $options, "addpage_$page->id");
487             $select->nothinglabel = get_string('addpage', 'lesson').'...';
489             $actions[] = $OUTPUT->select($select);
490         }
491     }
493     $actions = implode(' ', $actions);
495     if ($return) {
496         return $actions;
497     } else {
498         echo $actions;
499         return false;
500     }
503 /**
504  * Prints the add links in expanded view or single view when editing
505  *
506  * @uses $CFG
507  * @param int $cmid Course Module ID
508  * @param int $prevpageid Previous page id
509  * @param boolean $return Return flag
510  * @return mixed boolean/string
511  * @todo &amp;pageid does not make sense, it is prevpageid
512  **/
513 function lesson_print_add_links($cmid, $prevpageid, $return = false) {
514     global $CFG;
515     
516     $context = get_context_instance(CONTEXT_MODULE, $cmid);
517     
518     $links = '';
519     if (has_capability('mod/lesson:edit', $context)) {
520         $links = array();
521         $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/import.php?id=$cmid&amp;pageid=$prevpageid\">".
522                     get_string('importquestions', 'lesson').'</a>';
523         
524         $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addcluster&amp;pageid=$prevpageid\">".
525                     get_string('addcluster', 'lesson').'</a>';
526         
527         if ($prevpageid != 0) {
528             $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofcluster&amp;pageid=$prevpageid\">".
529                         get_string('addendofcluster', 'lesson').'</a>';
530         }
531         $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addbranchtable&amp;pageid=$prevpageid\">".
532                     get_string('addabranchtable', 'lesson').'</a>';
533         
534         if ($prevpageid != 0) {
535             $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;sesskey=".sesskey()."&amp;action=addendofbranch&amp;pageid=$prevpageid\">".
536                         get_string('addanendofbranch', 'lesson').'</a>';
537         }
538         
539         $links[] = "<a href=\"$CFG->wwwroot/mod/lesson/lesson.php?id=$cmid&amp;action=addpage&amp;pageid=$prevpageid\">".
540                     get_string('addaquestionpagehere', 'lesson').'</a>';
541         
542         $links = implode(" | \n", $links);
543         $links = "\n<div class=\"addlinks\">\n$links\n</div>\n";
544     }
546     if ($return) {
547         return $links;
548     } else {
549         echo $links;
550         return true;
551     }
554 /**
555  * Returns the string for a page type
556  *
557  * @uses $LESSON_QUESTION_TYPE
558  * @param int $qtype Page type
559  * @return string
560  **/
561 function lesson_get_qtype_name($qtype) {
562     global $LESSON_QUESTION_TYPE;
563     switch ($qtype) {
564         case LESSON_ESSAY :
565         case LESSON_SHORTANSWER :
566         case LESSON_MULTICHOICE :
567         case LESSON_MATCHING :
568         case LESSON_TRUEFALSE :
569         case LESSON_NUMERICAL :
570             return $LESSON_QUESTION_TYPE[$qtype];
571             break;
572         case LESSON_BRANCHTABLE :    
573             return get_string("branchtable", "lesson");
574             break;
575         case LESSON_ENDOFBRANCH :
576             return get_string("endofbranch", "lesson");
577             break;
578         case LESSON_CLUSTER :
579             return get_string("clustertitle", "lesson");
580             break;
581         case LESSON_ENDOFCLUSTER :
582             return get_string("endofclustertitle", "lesson");
583             break;
584         default:
585             return '';
586             break;
587     }
590 /**
591  * Returns the string for a jump name
592  *
593  * @param int $jumpto Jump code or page ID
594  * @return string
595  **/
596 function lesson_get_jump_name($jumpto) {
597     global $DB;
598     
599     if ($jumpto == 0) {
600         $jumptitle = get_string('thispage', 'lesson');
601     } elseif ($jumpto == LESSON_NEXTPAGE) {
602         $jumptitle = get_string('nextpage', 'lesson');
603     } elseif ($jumpto == LESSON_EOL) {
604         $jumptitle = get_string('endoflesson', 'lesson');
605     } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
606         $jumptitle = get_string('unseenpageinbranch', 'lesson');
607     } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
608         $jumptitle = get_string('previouspage', 'lesson');
609     } elseif ($jumpto == LESSON_RANDOMPAGE) {
610         $jumptitle = get_string('randompageinbranch', 'lesson');
611     } elseif ($jumpto == LESSON_RANDOMBRANCH) {
612         $jumptitle = get_string('randombranch', 'lesson');
613     } elseif ($jumpto == LESSON_CLUSTERJUMP) {
614         $jumptitle = get_string('clusterjump', 'lesson');
615     } else {
616         if (!$jumptitle = $DB->get_field('lesson_pages', 'title', array('id' => $jumpto))) {
617             $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
618         }
619     }
620     
621     return format_string($jumptitle,true);
624 /**
625  * Given some question info and some data about the the answers
626  * this function parses, organises and saves the question
627  *
628  * This is only used when IMPORTING questions and is only called
629  * from format.php
630  * Lifted from mod/quiz/lib.php - 
631  *    1. all reference to oldanswers removed
632  *    2. all reference to quiz_multichoice table removed
633  *    3. In SHORTANSWER questions usecase is store in the qoption field
634  *    4. In NUMERIC questions store the range as two answers
635  *    5. TRUEFALSE options are ignored
636  *    6. For MULTICHOICE questions with more than one answer the qoption field is true
637  * 
638  * @param opject $question Contains question data like question, type and answers.
639  * @return object Returns $result->error or $result->notice.
640  **/
641 function lesson_save_question_options($question) {
642     global $DB;
643     
644     $timenow = time();
645     switch ($question->qtype) {
646         case LESSON_SHORTANSWER:
648             $answers = array();
649             $maxfraction = -1;
651             // Insert all the new answers
652             foreach ($question->answer as $key => $dataanswer) {
653                 if ($dataanswer != "") {
654                     $answer = new stdClass;
655                     $answer->lessonid   = $question->lessonid;
656                     $answer->pageid   = $question->id;
657                     if ($question->fraction[$key] >=0.5) {
658                         $answer->jumpto = LESSON_NEXTPAGE;
659                     }
660                     $answer->timecreated   = $timenow;
661                     $answer->grade = $question->fraction[$key] * 100;
662                     $answer->answer   = $dataanswer;
663                     $answer->response = $question->feedback[$key];
664                     $answer->id = $DB->insert_record("lesson_answers", $answer);
665                     $answers[] = $answer->id;
666                     if ($question->fraction[$key] > $maxfraction) {
667                         $maxfraction = $question->fraction[$key];
668                     }
669                 }
670             }
673             /// Perform sanity checks on fractional grades
674             if ($maxfraction != 1) {
675                 $maxfraction = $maxfraction * 100;
676                 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
677                 return $result;
678             }
679             break;
681         case LESSON_NUMERICAL:   // Note similarities to SHORTANSWER
683             $answers = array();
684             $maxfraction = -1;
686             
687             // for each answer store the pair of min and max values even if they are the same 
688             foreach ($question->answer as $key => $dataanswer) {
689                 if ($dataanswer != "") {
690                     $answer = new stdClass;
691                     $answer->lessonid   = $question->lessonid;
692                     $answer->pageid   = $question->id;
693                     $answer->jumpto = LESSON_NEXTPAGE;
694                     $answer->timecreated   = $timenow;
695                     $answer->grade = $question->fraction[$key] * 100;
696                     $min = $question->answer[$key] - $question->tolerance[$key];
697                     $max = $question->answer[$key] + $question->tolerance[$key];
698                     $answer->answer   = $min.":".$max;
699                     // $answer->answer   = $question->min[$key].":".$question->max[$key]; original line for min/max
700                     $answer->response = $question->feedback[$key];
701                     $answer->id = $DB->insert_record("lesson_answers", $answer);
702                     
703                     $answers[] = $answer->id;
704                     if ($question->fraction[$key] > $maxfraction) {
705                         $maxfraction = $question->fraction[$key];
706                     }
707                 }
708             }
710             /// Perform sanity checks on fractional grades
711             if ($maxfraction != 1) {
712                 $maxfraction = $maxfraction * 100;
713                 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
714                 return $result;
715             }
716         break;
719         case LESSON_TRUEFALSE:
721             // the truth
722             $answer->lessonid   = $question->lessonid;
723             $answer->pageid = $question->id;
724             $answer->timecreated   = $timenow;
725             $answer->answer = get_string("true", "quiz");
726             $answer->grade = $question->answer * 100;
727             if ($answer->grade > 50 ) {
728                 $answer->jumpto = LESSON_NEXTPAGE;
729             }
730             if (isset($question->feedbacktrue)) {
731                 $answer->response = $question->feedbacktrue;
732             }
733             $true->id = $DB->insert_record("lesson_answers", $answer);
735             // the lie    
736             $answer = new stdClass;
737             $answer->lessonid   = $question->lessonid;
738             $answer->pageid = $question->id;
739             $answer->timecreated   = $timenow;
740             $answer->answer = get_string("false", "quiz");
741             $answer->grade = (1 - (int)$question->answer) * 100;
742             if ($answer->grade > 50 ) {
743                 $answer->jumpto = LESSON_NEXTPAGE;
744             }
745             if (isset($question->feedbackfalse)) {
746                 $answer->response = $question->feedbackfalse;
747             }
748             $false->id = $DB->insert_record("lesson_answers", $answer);
750           break;
753         case LESSON_MULTICHOICE:
755             $totalfraction = 0;
756             $maxfraction = -1;
758             $answers = array();
760             // Insert all the new answers
761             foreach ($question->answer as $key => $dataanswer) {
762                 if ($dataanswer != "") {
763                     $answer = new stdClass;
764                     $answer->lessonid   = $question->lessonid;
765                     $answer->pageid   = $question->id;
766                     $answer->timecreated   = $timenow;
767                     $answer->grade = $question->fraction[$key] * 100;
768                     // changed some defaults
769                     /* Original Code
770                     if ($answer->grade > 50 ) {
771                         $answer->jumpto = LESSON_NEXTPAGE;
772                     }
773                     Replaced with:                    */
774                     if ($answer->grade > 50 ) {
775                         $answer->jumpto = LESSON_NEXTPAGE;
776                         $answer->score = 1;
777                     }
778                     // end Replace
779                     $answer->answer   = $dataanswer;
780                     $answer->response = $question->feedback[$key];
781                     $answer->id = $DB->insert_record("lesson_answers", $answer);
782                     // for Sanity checks
783                     if ($question->fraction[$key] > 0) {                 
784                         $totalfraction += $question->fraction[$key];
785                     }
786                     if ($question->fraction[$key] > $maxfraction) {
787                         $maxfraction = $question->fraction[$key];
788                     }
789                 }
790             }
792             /// Perform sanity checks on fractional grades
793             if ($question->single) {
794                 if ($maxfraction != 1) {
795                     $maxfraction = $maxfraction * 100;
796                     $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
797                     return $result;
798                 }
799             } else {
800                 $totalfraction = round($totalfraction,2);
801                 if ($totalfraction != 1) {
802                     $totalfraction = $totalfraction * 100;
803                     $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
804                     return $result;
805                 }
806             }
807         break;
809         case LESSON_MATCHING:
811             $subquestions = array();
813             $i = 0;
814             // Insert all the new question+answer pairs
815             foreach ($question->subquestions as $key => $questiontext) {
816                 $answertext = $question->subanswers[$key];
817                 if (!empty($questiontext) and !empty($answertext)) {
818                     $answer = new stdClass;
819                     $answer->lessonid   = $question->lessonid;
820                     $answer->pageid   = $question->id;
821                     $answer->timecreated   = $timenow;
822                     $answer->answer = $questiontext;
823                     $answer->response   = $answertext; 
824                     if ($i == 0) {
825                         // first answer contains the correct answer jump
826                         $answer->jumpto = LESSON_NEXTPAGE;
827                     }
828                     $subquestion->id = $DB->insert_record("lesson_answers", $answer);
829                     $subquestions[] = $subquestion->id;
830                     $i++;
831                 }
832             }
834             if (count($subquestions) < 3) {
835                 $result->notice = get_string("notenoughsubquestions", "quiz");
836                 return $result;
837             }
839             break;
842         case LESSON_RANDOMSAMATCH:
843             $options->question = $question->id;
844             $options->choose = $question->choose;
845             if ($existing = $DB->get_record("quiz_randomsamatch", array("question" => $options->question))) {
846                 $options->id = $existing->id;
847                 $DB->update_record("quiz_randomsamatch", $options);
848             } else {
849                 $DB->insert_record("quiz_randomsamatch", $options);
850             }
851         break;
853         case LESSON_MULTIANSWER:
854             if (!$oldmultianswers = $DB->get_records("quiz_multianswers", array("question" => $question->id), "id ASC")) {
855                 $oldmultianswers = array();
856             }
858             // Insert all the new multi answers
859             foreach ($question->answers as $dataanswer) {
860                 if ($oldmultianswer = array_shift($oldmultianswers)) {  // Existing answer, so reuse it
861                     $multianswer = $oldmultianswer;
862                     $multianswer->positionkey = $dataanswer->positionkey;
863                     $multianswer->norm = $dataanswer->norm;
864                     $multianswer->answertype = $dataanswer->answertype;
866                     if (! $multianswer->answers = quiz_save_multianswer_alternatives
867                             ($question->id, $dataanswer->answertype,
868                              $dataanswer->alternatives, $oldmultianswer->answers))
869                     {
870                         $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
871                         return $result;
872                     }
873                     $DB->update_record("quiz_multianswers", $multianswer);
874                 } else {    // This is a completely new answer
875                     $multianswer = new stdClass;
876                     $multianswer->question = $question->id;
877                     $multianswer->positionkey = $dataanswer->positionkey;
878                     $multianswer->norm = $dataanswer->norm;
879                     $multianswer->answertype = $dataanswer->answertype;
881                     if (! $multianswer->answers = quiz_save_multianswer_alternatives
882                             ($question->id, $dataanswer->answertype,
883                              $dataanswer->alternatives))
884                     {
885                         $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
886                         return $result;
887                     }
888                     $DB->insert_record("quiz_multianswers", $multianswer);
889                 }
890             }
891         break;
893         case LESSON_RANDOM:
894         break;
896         case LESSON_DESCRIPTION:
897         break;
899         default:
900             $result->error = "Unsupported question type ($question->qtype)!";
901             return $result;
902         break;
903     }
904     return true;
907 /**
908  * Determins if a jumpto value is correct or not.
909  *
910  * returns true if jumpto page is (logically) after the pageid page or
911  * if the jumpto value is a special value.  Returns false in all other cases.
912  * 
913  * @param int $pageid Id of the page from which you are jumping from.
914  * @param int $jumpto The jumpto number.
915  * @return boolean True or false after a series of tests.
916  **/
917 function lesson_iscorrect($pageid, $jumpto) {
918     global $DB;
919     
920     // first test the special values
921     if (!$jumpto) {
922         // same page
923         return false;
924     } elseif ($jumpto == LESSON_NEXTPAGE) {
925         return true;
926     } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
927         return true;
928     } elseif ($jumpto == LESSON_RANDOMPAGE) {
929         return true;
930     } elseif ($jumpto == LESSON_CLUSTERJUMP) {
931         return true;
932     } elseif ($jumpto == LESSON_EOL) {
933         return true;
934     }
935     // we have to run through the pages from pageid looking for jumpid
936     if ($lessonid = $DB->get_field('lesson_pages', 'lessonid', array('id' => $pageid))) {
937         if ($pages = $DB->get_records('lesson_pages', array('lessonid' => $lessonid), '', 'id, nextpageid')) {
938             $apageid = $pages[$pageid]->nextpageid;
939             while ($apageid != 0) {
940                 if ($jumpto == $apageid) {
941                     return true;
942                 }
943                 $apageid = $pages[$apageid]->nextpageid;
944             }
945         }
946     }
947     return false;
950 /**
951  * Checks to see if a page is a branch table or is
952  * a page that is enclosed by a branch table and an end of branch or end of lesson.
953  * May call this function: {@link lesson_is_page_in_branch()}
954  *
955  * @param int $lesson Id of the lesson to which the page belongs.
956  * @param int $pageid Id of the page.
957  * @return boolean True or false.
958  **/
959 function lesson_display_branch_jumps($lessonid, $pageid) {
960     global $DB;
961     
962     if($pageid == 0) {
963         // first page
964         return false;
965     }
966     // get all of the lesson pages
967     $params = array ("lessonid" => $lessonid);
968     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
969         // adding first page
970         return false;
971     }
973     if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
974         return true;
975     }
976     
977     return lesson_is_page_in_branch($lessonpages, $pageid);
980 /**
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()}
984  * 
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.
988  **/
989 function lesson_display_cluster_jump($lesson, $pageid) {
990     global $DB;
991     
992     if($pageid == 0) {
993         // first page
994         return false;
995     }
996     // get all of the lesson pages
997     $params = array ("lessonid" => $lesson);
998     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
999         // adding first page
1000         return false;
1001     }
1003     if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1004         return true;
1005     }
1006     
1007     return lesson_is_page_in_cluster($lessonpages, $pageid);
1011 /**
1012  * Checks to see if a LESSON_CLUSTERJUMP or 
1013  * a LESSON_UNSEENBRANCHPAGE is used in a lesson.
1014  *
1015  * This function is only executed when a teacher is 
1016  * checking the navigation for a lesson.
1017  *
1018  * @param int $lesson Id of the lesson that is to be checked.
1019  * @return boolean True or false.
1020  **/
1021 function lesson_display_teacher_warning($lesson) {
1022     global $DB;
1023     
1024     // get all of the lesson answers
1025     $params = array ("lessonid" => $lesson);
1026     if (!$lessonanswers = $DB->get_records_select("lesson_answers", "lessonid = :lessonid", $params)) {
1027         // no answers, then not useing cluster or unseen
1028         return false;
1029     }
1030     // just check for the first one that fulfills the requirements
1031     foreach ($lessonanswers as $lessonanswer) {
1032         if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) {
1033             return true;
1034         }
1035     }
1036     
1037     // if no answers use either of the two jumps
1038     return false;
1042 /**
1043  * Interprets LESSON_CLUSTERJUMP jumpto value.
1044  *
1045  * This will select a page randomly
1046  * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
1047  * and the page selected will be a page that has not been viewed already
1048  * and if any pages are within a branch table or end of branch then only 1 page within 
1049  * the branch table or end of branch will be randomly selected (sub clustering).
1050  * 
1051  * @param int $lessonid Id of the lesson.
1052  * @param int $userid Id of the user.
1053  * @param int $pageid Id of the current page from which we are jumping from.
1054  * @return int The id of the next page.
1055  **/
1056 function lesson_cluster_jump($lessonid, $userid, $pageid) {
1057     global $DB;
1058     
1059     // get the number of retakes
1060     if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lessonid, "userid"=>$userid))) {
1061         $retakes = 0;
1062     }
1064     // get all the lesson_attempts aka what the user has seen
1065     $params = array ("lessonid" => $lessonid, "userid" => $userid, "retry" => $retakes);
1066     if ($seen = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid AND retry = :retry", $params, "timeseen DESC")) {
1067         foreach ($seen as $value) { // load it into an array that I can more easily use
1068             $seenpages[$value->pageid] = $value->pageid;
1069         }
1070     } else {
1071         $seenpages = array();
1072     }
1074     // get the lesson pages
1075     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
1076         print_error('cannotfindrecords', 'lesson');
1077     }
1078     // find the start of the cluster
1079     while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
1080         if ($lessonpages[$pageid]->qtype == LESSON_CLUSTER) {
1081             break;
1082         }
1083         $pageid = $lessonpages[$pageid]->prevpageid;
1084     }
1086     $pageid = $lessonpages[$pageid]->nextpageid; // move down from the cluster page
1087     
1088     $clusterpages = array();
1089     while (true) {  // now load all the pages into the cluster that are not already inside of a branch table.
1090         if ($lessonpages[$pageid]->qtype == LESSON_ENDOFCLUSTER) {
1091             // store the endofcluster page's jump
1092             $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $pageid, "lessonid" => $lessonid));
1093             if ($exitjump == LESSON_NEXTPAGE) {
1094                 $exitjump = $lessonpages[$pageid]->nextpageid;
1095             }
1096             if ($exitjump == 0) {
1097                 $exitjump = LESSON_EOL;
1098             }
1099             break;
1100         } elseif (!lesson_is_page_in_branch($lessonpages, $pageid) && $lessonpages[$pageid]->qtype != LESSON_ENDOFBRANCH) {
1101             // load page into array when it is not in a branch table and when it is not an endofbranch
1102             $clusterpages[] = $lessonpages[$pageid];
1103         }
1104         if ($lessonpages[$pageid]->nextpageid == 0) {
1105             // shouldn't ever get here... should be using endofcluster
1106             $exitjump = LESSON_EOL;
1107             break;
1108         } else {
1109             $pageid = $lessonpages[$pageid]->nextpageid;
1110         }
1111     }
1113     // filter out the ones we have seen
1114     $unseen = array();
1115     foreach ($clusterpages as $clusterpage) {
1116         if ($clusterpage->qtype == LESSON_BRANCHTABLE) {            // if branchtable, check to see if any pages inside have been viewed
1117             $branchpages = lesson_pages_in_branch($lessonpages, $clusterpage->id); // get the pages in the branchtable
1118             $flag = true;
1119             foreach ($branchpages as $branchpage) {
1120                 if (array_key_exists($branchpage->id, $seenpages)) {  // check if any of the pages have been viewed
1121                     $flag = false;
1122                 }
1123             }
1124             if ($flag && count($branchpages) > 0) {
1125                 // add branch table
1126                 $unseen[] = $clusterpage;
1127             }        
1128         } else {
1129             // add any other type of page that has not already been viewed
1130             if (!array_key_exists($clusterpage->id, $seenpages)) {
1131                 $unseen[] = $clusterpage;
1132             }
1133         }
1134     }
1136     if (count($unseen) > 0) { // it does not contain elements, then use exitjump, otherwise find out next page/branch
1137         $nextpage = $unseen[rand(0, count($unseen)-1)];
1138     } else {
1139         return $exitjump; // seen all there is to see, leave the cluster
1140     }
1141     
1142     if ($nextpage->qtype == LESSON_BRANCHTABLE) { // if branch table, then pick a random page inside of it
1143         $branchpages = lesson_pages_in_branch($lessonpages, $nextpage->id);
1144         return $branchpages[rand(0, count($branchpages)-1)]->id;
1145     } else { // otherwise, return the page's id
1146         return $nextpage->id;
1147     }
1150 /**
1151  * Returns pages that are within a branch table and another branch table, end of branch or end of lesson
1152  * 
1153  * @param array $lessonpages An array of lesson page objects.
1154  * @param int $branchid The id of the branch table that we would like the containing pages for.
1155  * @return array An array of lesson page objects.
1156  **/
1157 function lesson_pages_in_branch($lessonpages, $branchid) {
1158     $pageid = $lessonpages[$branchid]->nextpageid;  // move to the first page after the branch table
1159     $pagesinbranch = array();
1160     
1161     while (true) { 
1162         if ($pageid == 0) { // EOL
1163             break;
1164         } elseif ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1165             break;
1166         } elseif ($lessonpages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1167             break;
1168         }
1169         $pagesinbranch[] = $lessonpages[$pageid];
1170         $pageid = $lessonpages[$pageid]->nextpageid;
1171     }
1172     
1173     return $pagesinbranch;
1176 /**
1177  * Interprets the LESSON_UNSEENBRANCHPAGE jump.
1178  * 
1179  * will return the pageid of a random unseen page that is within a branch
1180  *
1181  * @see lesson_pages_in_branch()
1182  * @param int $lesson Id of the lesson.
1183  * @param int $userid Id of the user.
1184  * @param int $pageid Id of the page from which we are jumping.
1185  * @return int Id of the next page.
1186  **/
1187 function lesson_unseen_question_jump($lesson, $user, $pageid) {
1188     global $DB;
1189     
1190     // get the number of retakes
1191     if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson, "userid"=>$user))) {
1192         $retakes = 0;
1193     }
1195     // get all the lesson_attempts aka what the user has seen
1196     $params = array ("lessonid" => $lesson, "userid" => $user, "retry" => $retakes);
1197     if ($viewedpages = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid AND retry = :retry", $params, "timeseen DESC")) {
1198         foreach($viewedpages as $viewed) {
1199             $seenpages[] = $viewed->pageid;
1200         }
1201     } else {
1202         $seenpages = array();
1203     }
1205     // get the lesson pages
1206     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
1207         print_error('cannotfindpages', 'lesson');
1208     }
1209     
1210     if ($pageid == LESSON_UNSEENBRANCHPAGE) {  // this only happens when a student leaves in the middle of an unseen question within a branch series
1211         $pageid = $seenpages[0];  // just change the pageid to the last page viewed inside the branch table
1212     }
1214     // go up the pages till branch table
1215     while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1216         if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1217             break;
1218         }
1219         $pageid = $lessonpages[$pageid]->prevpageid;
1220     }
1221     
1222     $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1223     
1224     // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array
1225     $unseen = array();
1226     foreach($pagesinbranch as $page) {    
1227         if (!in_array($page->id, $seenpages)) {
1228             $unseen[] = $page->id;
1229         }
1230     }
1232     if(count($unseen) == 0) {
1233         if(isset($pagesinbranch)) {
1234             $temp = end($pagesinbranch);
1235             $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL
1236         } else {
1237             // there are no pages inside the branch, so return the next page
1238             $nextpage = $lessonpages[$pageid]->nextpageid;
1239         }
1240         if ($nextpage == 0) {
1241             return LESSON_EOL;
1242         } else {
1243             return $nextpage;
1244         }
1245     } else {
1246         return $unseen[rand(0, count($unseen)-1)];  // returns a random page id for the next page
1247     }
1250 /**
1251  * Handles the unseen branch table jump.
1252  *
1253  * @param int $lessonid Lesson id.
1254  * @param int $userid User id.
1255  * @return int Will return the page id of a branch table or end of lesson
1256  **/
1257 function lesson_unseen_branch_jump($lessonid, $userid) {
1258     global $DB;
1259     
1260     if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lessonid, "userid"=>$userid))) {
1261         $retakes = 0;
1262     }
1264     $params = array ("lessonid" => $lessonid, "userid" => $userid, "retry" => $retakes);
1265     if (!$seenbranches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid AND retry = :retry", $params,
1266                 "timeseen DESC")) {
1267         print_error('cannotfindrecords', 'lesson');
1268     }
1270     // get the lesson pages
1271     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
1272         print_error('cannotfindpages', 'lesson');
1273     }
1274     
1275     // this loads all the viewed branch tables into $seen untill it finds the branch table with the flag
1276     // which is the branch table that starts the unseenbranch function
1277     $seen = array();    
1278     foreach ($seenbranches as $seenbranch) {
1279         if (!$seenbranch->flag) {
1280             $seen[$seenbranch->pageid] = $seenbranch->pageid;
1281         } else {
1282             $start = $seenbranch->pageid;
1283             break;
1284         }
1285     }
1286     // this function searches through the lesson pages to find all the branch tables
1287     // that follow the flagged branch table
1288     $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table
1289     while ($pageid != 0) {  // grab all of the branch table till eol
1290         if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1291             $branchtables[] = $lessonpages[$pageid]->id;
1292         }
1293         $pageid = $lessonpages[$pageid]->nextpageid;
1294     }
1295     $unseen = array();
1296     foreach ($branchtables as $branchtable) {
1297         // load all of the unseen branch tables into unseen
1298         if (!array_key_exists($branchtable, $seen)) {
1299             $unseen[] = $branchtable;
1300         }
1301     }
1302     if (count($unseen) > 0) {
1303         return $unseen[rand(0, count($unseen)-1)];  // returns a random page id for the next page
1304     } else {
1305         return LESSON_EOL;  // has viewed all of the branch tables
1306     }
1309 /**
1310  * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE).
1311  * 
1312  * @param int $lessonid Lesson id.
1313  * @param int $pageid The id of the page that we are jumping from (?)
1314  * @return int The pageid of a random page that is within a branch table
1315  **/
1316 function lesson_random_question_jump($lessonid, $pageid) {
1317     global $DB;
1318     
1319     // get the lesson pages
1320     $params = array ("lessonid" => $lessonid);
1321     if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) {
1322         print_error('cannotfindpages', 'lesson');
1323     }
1325     // go up the pages till branch table
1326     while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page
1328         if ($lessonpages[$pageid]->qtype == LESSON_BRANCHTABLE) {
1329             break;
1330         }
1331         $pageid = $lessonpages[$pageid]->prevpageid;
1332     }
1334     // get the pages within the branch    
1335     $pagesinbranch = lesson_pages_in_branch($lessonpages, $pageid);
1336     
1337     if(count($pagesinbranch) == 0) {
1338         // there are no pages inside the branch, so return the next page
1339         return $lessonpages[$pageid]->nextpageid;
1340     } else {
1341         return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id;  // returns a random page id for the next page
1342     }
1345 /**
1346  * Check to see if a page is below a branch table (logically).
1347  * 
1348  * Will return true if a branch table is found logically above the page.
1349  * Will return false if an end of branch, cluster or the beginning
1350  * of the lesson is found before a branch table.
1351  *
1352  * @param array $pages An array of lesson page objects.
1353  * @param int $pageid Id of the page for testing.
1354  * @return boolean
1355  */
1356 function lesson_is_page_in_branch($pages, $pageid) {
1357     $pageid = $pages[$pageid]->prevpageid; // move up one
1359     // go up the pages till branch table    
1360     while (true) {
1361         if ($pageid == 0) {  // ran into the beginning of the lesson
1362             return false;
1363         } elseif ($pages[$pageid]->qtype == LESSON_ENDOFBRANCH) { // ran into the end of another branch table
1364             return false;
1365         } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // do not look beyond a cluster
1366             return false;
1367         } elseif ($pages[$pageid]->qtype == LESSON_BRANCHTABLE) { // hit a branch table
1368             return true;
1369         }
1370         $pageid = $pages[$pageid]->prevpageid;
1371     }
1375 /**
1376  * Check to see if a page is below a cluster page (logically).
1377  * 
1378  * Will return true if a cluster is found logically above the page.
1379  * Will return false if an end of cluster or the beginning
1380  * of the lesson is found before a cluster page.
1381  *
1382  * @param array $pages An array of lesson page objects.
1383  * @param int $pageid Id of the page for testing.
1384  * @return boolean
1385  */
1386 function lesson_is_page_in_cluster($pages, $pageid) {
1387     $pageid = $pages[$pageid]->prevpageid; // move up one
1389     // go up the pages till branch table    
1390     while (true) {
1391         if ($pageid == 0) {  // ran into the beginning of the lesson
1392             return false;
1393         } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER) { // ran into the end of another branch table
1394             return false;
1395         } elseif ($pages[$pageid]->qtype == LESSON_CLUSTER) { // hit a branch table
1396             return true;
1397         }
1398         $pageid = $pages[$pageid]->prevpageid;
1399     }
1402 /**
1403  * Calculates a user's grade for a lesson.
1404  *
1405  * @param object $lesson The lesson that the user is taking.
1406  * @param int $retries The attempt number.
1407  * @param int $userid Id of the user (optinal, default current user).
1408  * @return object { nquestions => number of questions answered
1409                     attempts => number of question attempts
1410                     total => max points possible
1411                     earned => points earned by student
1412                     grade => calculated percentage grade
1413                     nmanual => number of manually graded questions
1414                     manualpoints => point value for manually graded questions }
1415  */
1416 function lesson_grade($lesson, $ntries, $userid = 0) {  
1417     global $USER, $DB;
1419     if (empty($userid)) {
1420         $userid = $USER->id;
1421     }
1422     
1423     // Zero out everything
1424     $ncorrect     = 0;
1425     $nviewed      = 0;
1426     $score        = 0;
1427     $nmanual      = 0;
1428     $manualpoints = 0;
1429     $thegrade     = 0;
1430     $nquestions   = 0;
1431     $total        = 0;
1432     $earned       = 0;
1434     $params = array ("lessonid" => $lesson->id, "userid" => $userid, "retry" => $ntries);
1435     if ($useranswers = $DB->get_records_select("lesson_attempts",  "lessonid = :lessonid AND 
1436             userid = :userid AND retry = :retry", $params, "timeseen")) {
1437         // group each try with its page
1438         $attemptset = array();
1439         foreach ($useranswers as $useranswer) {
1440             $attemptset[$useranswer->pageid][] = $useranswer;                                
1441         }
1442         
1443         // Drop all attempts that go beyond max attempts for the lesson
1444         foreach ($attemptset as $key => $set) {
1445             $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts);
1446         }
1447         
1448         // get only the pages and their answers that the user answered
1449         list($usql, $parameters) = $DB->get_in_or_equal(array_keys($attemptset));
1450         $parameters["lessonid"] = $lesson->id;
1451         $pages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid AND id $usql", $parameters);
1452         $answers = $DB->get_records_select("lesson_answers", "lessonid = :lessonid AND pageid $usql", $parameters);
1453         
1454         // Number of pages answered
1455         $nquestions = count($pages);
1457         foreach ($attemptset as $attempts) {
1458             if ($lesson->custom) {
1459                 $attempt = end($attempts);
1460                 // If essay question, handle it, otherwise add to score
1461                 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1462                     $essayinfo = unserialize($attempt->useranswer);
1463                     $earned += $essayinfo->score;
1464                     $nmanual++;
1465                     $manualpoints += $answers[$attempt->answerid]->score;
1466                 } else if (!empty($attempt->answerid)) {
1467                     $earned += $answers[$attempt->answerid]->score;
1468                 }
1469             } else {
1470                 foreach ($attempts as $attempt) {
1471                     $earned += $attempt->correct;
1472                 }
1473                 $attempt = end($attempts); // doesn't matter which one
1474                 // If essay question, increase numbers
1475                 if ($pages[$attempt->pageid]->qtype == LESSON_ESSAY) {
1476                     $nmanual++;
1477                     $manualpoints++;
1478                 }
1479             }
1480             // Number of times answered
1481             $nviewed += count($attempts);
1482         }
1483         
1484         if ($lesson->custom) {
1485             $bestscores = array();
1486             // Find the highest possible score per page to get our total
1487             foreach ($answers as $answer) {
1488                 if(!isset($bestscores[$answer->pageid])) {
1489                     $bestscores[$answer->pageid] = $answer->score;
1490                 } else if ($bestscores[$answer->pageid] < $answer->score) {
1491                     $bestscores[$answer->pageid] = $answer->score;
1492                 }
1493             }
1494             $total = array_sum($bestscores);
1495         } else {
1496             // Check to make sure the student has answered the minimum questions
1497             if ($lesson->minquestions and $nquestions < $lesson->minquestions) {
1498                 // Nope, increase number viewed by the amount of unanswered questions
1499                 $total =  $nviewed + ($lesson->minquestions - $nquestions);
1500             } else {
1501                 $total = $nviewed;
1502             }
1503         }
1504     }
1505     
1506     if ($total) { // not zero
1507         $thegrade = round(100 * $earned / $total, 5);
1508     }
1509     
1510     // Build the grade information object
1511     $gradeinfo               = new stdClass;
1512     $gradeinfo->nquestions   = $nquestions;
1513     $gradeinfo->attempts     = $nviewed;
1514     $gradeinfo->total        = $total;
1515     $gradeinfo->earned       = $earned;
1516     $gradeinfo->grade        = $thegrade;
1517     $gradeinfo->nmanual      = $nmanual;
1518     $gradeinfo->manualpoints = $manualpoints;
1519     
1520     return $gradeinfo;
1523 /**
1524  * Prints the on going message to the user.
1525  *
1526  * With custom grading On, displays points 
1527  * earned out of total points possible thus far.
1528  * With custom grading Off, displays number of correct
1529  * answers out of total attempted.
1530  *
1531  * @param object $lesson The lesson that the user is taking.
1532  * @return void
1533  **/
1534 function lesson_print_ongoing_score($lesson) {
1535     global $USER, $DB, $OUTPUT;
1537     $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1538     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1540     if (has_capability('mod/lesson:manage', $context)) {
1541         echo "<p align=\"center\">".get_string('teacherongoingwarning', 'lesson').'</p>';
1542     } else {
1543         $ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
1544         if (isset($USER->modattempts[$lesson->id])) {
1545             $ntries--;
1546         }
1547         $gradeinfo = lesson_grade($lesson, $ntries);
1548         
1549         $a = new stdClass;
1550         if ($lesson->custom) {
1551             $a->score = $gradeinfo->earned;
1552             $a->currenthigh = $gradeinfo->total;
1553             echo $OUTPUT->box(get_string("ongoingcustom", "lesson", $a), "generalbox boxaligncenter");
1554         } else {
1555             $a->correct = $gradeinfo->earned;
1556             $a->viewed = $gradeinfo->attempts;
1557             echo $OUTPUT->box(get_string("ongoingnormal", "lesson", $a), "generalbox boxaligncenter");
1558         }
1559     }
1562 /**
1563  * Prints tabs for the editing and adding pages.  Each tab is a question type.
1564  *  
1565  * @param array $qtypes The question types array (may not need to pass this because it is defined in this file)
1566  * @param string $selected Current selected tab
1567  * @param string $link The base href value of the link for the tab
1568  * @param string $onclick Javascript for the tab link
1569  * @return void
1570  */
1571 function lesson_qtype_menu($qtypes, $selected="", $link="", $onclick="") {
1572     $tabs = array();
1573     $tabrows = array();
1575     foreach ($qtypes as $qtype => $qtypename) {
1576         $tabrows[] = new tabobject($qtype, "$link&amp;qtype=$qtype\" onclick=\"$onclick", $qtypename);
1577     }
1578     $tabs[] = $tabrows;
1579     print_tabs($tabs, $selected);
1580     echo "<input type=\"hidden\" name=\"qtype\" value=\"$selected\" /> \n";
1584 /**
1585  * Prints out a Progress Bar which depicts a user's progress within a lesson.
1586  *
1587  * Currently works best with a linear lesson.  Clusters are counted as a single page.
1588  * Also, only viewed branch tables and questions that have been answered correctly count
1589  * toward lesson completion (or progress).  Only Students can see the Progress bar as well.
1590  *
1591  * @param object $lesson The lesson that the user is currently taking.
1592  * @param object $course The course that the to which the lesson belongs.
1593  * @return boolean The return is not significant as of yet.  Will return true/false.
1594  **/
1595 function lesson_print_progress_bar($lesson, $course) {
1596     global $CFG, $USER, $DB, $OUTPUT;
1598     $cm = get_coursemodule_from_instance('lesson', $lesson->id);
1599     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1601     // lesson setting to turn progress bar on or off
1602     if (!$lesson->progressbar) {
1603         return false;
1604     }
1605     
1606     // catch teachers
1607     if (has_capability('mod/lesson:manage', $context)) {
1608         echo $OUTPUT->notification(get_string('progressbarteacherwarning2', 'lesson'));
1609         return false;
1610     }
1611     if (!isset($USER->modattempts[$lesson->id])) {
1612         // all of the lesson pages
1613         if (!$pages = $DB->get_records('lesson_pages', array('lessonid' => $lesson->id))) {
1614             return false;
1615         } else {
1616             foreach ($pages as $page) {
1617                 if ($page->prevpageid == 0) {
1618                     $pageid = $page->id;  // find the first page id
1619                     break;
1620                 }
1621             }
1622         }
1623     
1624         // current attempt number
1625         if (!$ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id))) {
1626             $ntries = 0;  // may not be necessary
1627         }
1628     
1629         $viewedpageids = array();
1630     
1631         // collect all of the correctly answered questions
1632         $params = array ("lessonid" => $lesson->id, "userid" => $USER->id, "retry" => $ntries);
1633         if ($viewedpages = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid AND retry = :retry AND correct = 1", $params, 'timeseen DESC', 'pageid, id')) {
1634             $viewedpageids = array_keys($viewedpages);
1635         }
1636         // collect all of the branch tables viewed
1637         if ($viewedbranches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid AND retry = :retry", $params, 'timeseen DESC', 'pageid, id')) {
1638             $viewedpageids = array_merge($viewedpageids, array_keys($viewedbranches));
1639         }
1641         // Filter out the following pages:
1642         //      End of Cluster
1643         //      End of Branch
1644         //      Pages found inside of Clusters
1645         // Do not filter out Cluster Page(s) because we count a cluster as one.
1646         // By keeping the cluster page, we get our 1
1647         $validpages = array(); 
1648         while ($pageid != 0) {
1649             if ($pages[$pageid]->qtype == LESSON_CLUSTER) {
1650                 $clusterpageid = $pageid; // copy it
1651                 $validpages[$clusterpageid] = 1;  // add the cluster page as a valid page
1652                 $pageid = $pages[$pageid]->nextpageid;  // get next page
1653             
1654                 // now, remove all necessary viewed paged ids from the viewedpageids array.
1655                 while ($pages[$pageid]->qtype != LESSON_ENDOFCLUSTER and $pageid != 0) {
1656                     if (in_array($pageid, $viewedpageids)) {
1657                         unset($viewedpageids[array_search($pageid, $viewedpageids)]);  // remove it
1658                         // since the user did see one page in the cluster, add the cluster pageid to the viewedpageids
1659                         if (!in_array($clusterpageid, $viewedpageids)) { 
1660                             $viewedpageids[] = $clusterpageid;
1661                         }
1662                     }
1663                     $pageid = $pages[$pageid]->nextpageid;
1664                 }
1665             } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER or $pages[$pageid]->qtype == LESSON_ENDOFBRANCH) {
1666                 // dont count these, just go to next
1667                 $pageid = $pages[$pageid]->nextpageid;
1668             } else {
1669                 // a counted page
1670                 $validpages[$pageid] = 1;
1671                 $pageid = $pages[$pageid]->nextpageid;
1672             }
1673         }    
1674     
1675         // progress calculation as a percent
1676         $progress = round(count($viewedpageids)/count($validpages), 2) * 100; 
1677     } else {
1678         $progress = 100;
1679     }
1681     // print out the Progress Bar.  Attempted to put as much as possible in the style sheets.
1682     echo '<div class="progress_bar" align="center">';
1683     echo '<table class="progress_bar_table"><tr>';
1684     if ($progress != 0) {  // some browsers do not repsect the 0 width.
1685         echo '<td style="width:'.$progress.'%;" class="progress_bar_completed">';
1686         echo '</td>';
1687     }
1688     echo '<td class="progress_bar_todo">';
1689     echo '<div class="progress_bar_token"></div>';
1690     echo '</td>';
1691     echo '</tr></table>';
1692     echo '</div>';
1693     
1694     return true;
1697 /**
1698  * Determines if a user can view the left menu.  The determining factor
1699  * is whether a user has a grade greater than or equal to the lesson setting
1700  * of displayleftif
1701  *
1702  * @param object $lesson Lesson object of the current lesson
1703  * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged
1704  **/
1705 function lesson_displayleftif($lesson) {
1706     global $CFG, $USER, $DB;
1707     
1708     if (!empty($lesson->displayleftif)) {
1709         // get the current user's max grade for this lesson
1710         $params = array ("userid" => $USER->id, "lessonid" => $lesson->id);
1711         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)) {
1712             if ($maxgrade->maxgrade < $lesson->displayleftif) {
1713                 return 0;  // turn off the displayleft
1714             }
1715         } else {
1716             return 0; // no grades
1717         }
1718     }
1719     
1720     // if we get to here, keep the original state of displayleft lesson setting
1721     return $lesson->displayleft;
1724 /**
1725  * 
1726  * @param $cm
1727  * @param $lesson
1728  * @param $page
1729  * @return unknown_type
1730  */
1731 function lesson_add_pretend_blocks($page, $cm, $lesson, $timer = null) {
1732     $bc = lesson_menu_block_contents($cm->id, $lesson);
1733     if (!empty($bc)) {
1734         $regions = $page->blocks->get_regions();
1735         $firstregion = reset($regions);
1736         $page->blocks->add_pretend_block($bc, $firstregion);
1737     }
1739     $bc = lesson_mediafile_block_contents($cm->id, $lesson);
1740     if (!empty($bc)) {
1741         $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
1742     }
1744     if (!empty($timer)) {
1745         $bc = lesson_clock_block_contents($cm->id, $lesson, $timer, $page);
1746         if (!empty($bc)) {
1747             $page->blocks->add_pretend_block($bc, $page->blocks->get_default_region());
1748         }
1749     }
1752 /**
1753  * If there is a media file associated with this 
1754  * lesson, return a block_contents that displays it.
1755  *
1756  * @param int $cmid Course Module ID for this lesson
1757  * @param object $lesson Full lesson record object
1758  * @return block_contents
1759  **/
1760 function lesson_mediafile_block_contents($cmid, $lesson) {
1761     global $OUTPUT;
1762     if (empty($lesson->mediafile)) {
1763         return null;
1764     }
1766     $url      = '/mod/lesson/mediafile.php?id='.$cmid;
1767     $options  = 'menubar=0,location=0,left=5,top=5,scrollbars,resizable,width='. $lesson->mediawidth .',height='. $lesson->mediaheight;
1768     $name     = 'lessonmediafile';
1770     $link = html_link::make($url, get_string('mediafilepopup', 'lesson'));
1771     $link->add_action(new popup_action('click', $link->url, $name, $options));
1772     $link->title = get_string('mediafilepopup', 'lesson');
1773     $content .= $OUTPUT->link($link);                    
1774     
1775     $content .= $OUTPUT->help_icon(moodle_help_icon::make("mediafilestudent", get_string("mediafile", "lesson"), "lesson"));
1777     $bc = new block_contents();
1778     $bc->title = get_string('linkedmedia', 'lesson');
1779     $bc->set_classes('mediafile');
1780     $bc->content = $content;
1782     return $bc;
1785 /**
1786  * If a timed lesson and not a teacher, then
1787  * return a block_contents containing the clock.
1788  *
1789  * @param int $cmid Course Module ID for this lesson
1790  * @param object $lesson Full lesson record object
1791  * @param object $timer Full timer record object
1792  * @return block_contents
1793  **/
1794 function lesson_clock_block_contents($cmid, $lesson, $timer, $page) {
1795     // Display for timed lessons and for students only
1796     $context = get_context_instance(CONTEXT_MODULE, $cmid);
1797     if(!$lesson->timed || has_capability('mod/lesson:manage', $context)) {
1798         return null;
1799     }
1801     $content = '<div class="jshidewhenenabled">';
1802     $content .= lesson_print_time_remaining($timer->starttime, $lesson->maxtime, true)."\n";
1803     $content .= '</div>';
1805     $clocksettings = array('starttime'=>$timer->starttime, 'servertime'=>time(),'testlength'=>($lesson->maxtime * 60));
1806     $content .= $page->requires->data_for_js('clocksettings', $clocksettings)->now();
1807     $content .= $page->requires->js('mod/lesson/timer.js')->now();
1808     $content .= $page->requires->js_function_call('show_clock')->now();
1810     $bc = new block_contents();
1811     $bc->title = get_string('timeremaining', 'lesson');
1812     $bc->set_classes('clock');
1813     $bc->content = $content;
1815     return $bc;
1818 /**
1819  * If left menu is turned on, then this will
1820  * print the menu in a block
1821  *
1822  * @param int $cmid Course Module ID for this lesson
1823  * @param object $lesson Full lesson record object
1824  * @return void
1825  **/
1826 function lesson_menu_block_contents($cmid, $lesson) {
1827     global $CFG, $DB;
1829     if (!$lesson->displayleft) {
1830         return null;
1831     }
1833     $pageid = $DB->get_field('lesson_pages', 'id', array('lessonid' => $lesson->id, 'prevpageid' => 0));
1834     $params = array ("lessonid" => $lesson->id);
1835     $pages  = $DB->get_records_select('lesson_pages', "lessonid = :lessonid", $params);
1836     $currentpageid = optional_param('pageid', $pageid, PARAM_INT);
1838     if (!$pageid || !$pages) {
1839         return null;
1840     }
1842     $content = '<a href="#maincontent" class="skip">'.get_string('skip', 'lesson')."</a>\n<div class=\"menuwrapper\">\n<ul>\n";
1844     while ($pageid != 0) {
1845         $page = $pages[$pageid];
1847         // Only process branch tables with display turned on
1848         if ($page->qtype == LESSON_BRANCHTABLE and $page->display) {
1849             if ($page->id == $currentpageid) { 
1850                 $content .= '<li class="selected">'.format_string($page->title,true)."</li>\n";
1851             } else {
1852                 $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";
1853             }
1854             
1855         }
1856         $pageid = $page->nextpageid;
1857     }
1858     $content .= "</ul>\n</div>\n";
1860     $bc = new block_contents();
1861     $bc->title = get_string('lessonmenu', 'lesson');
1862     $bc->set_classes('menu');
1863     $bc->content = $content;
1865     return $bc;