any page not seen before */ if (!defined("LESSON_UNSEENPAGE")) { define("LESSON_UNSEENPAGE", 1); // Next page -> any page not seen before } /** * Next page -> any page not answered correctly */ if (!defined("LESSON_UNANSWEREDPAGE")) { define("LESSON_UNANSWEREDPAGE", 2); // Next page -> any page not answered correctly } /** * Define different lesson flows for next page */ $LESSON_NEXTPAGE_ACTION = array (0 => get_string("normal", "lesson"), LESSON_UNSEENPAGE => get_string("showanunseenpage", "lesson"), LESSON_UNANSWEREDPAGE => get_string("showanunansweredpage", "lesson") ); // Lesson jump types defined // TODO: instead of using define statements, create an array with all the jump values /** * Jump to Next Page */ if (!defined("LESSON_NEXTPAGE")) { define("LESSON_NEXTPAGE", -1); } /** * End of Lesson */ if (!defined("LESSON_EOL")) { define("LESSON_EOL", -9); } /** * Jump to an unseen page within a branch and end of branch or end of lesson */ if (!defined("LESSON_UNSEENBRANCHPAGE")) { define("LESSON_UNSEENBRANCHPAGE", -50); } /** * Jump to Previous Page */ if (!defined("LESSON_PREVIOUSPAGE")) { define("LESSON_PREVIOUSPAGE", -40); } /** * Jump to a random page within a branch and end of branch or end of lesson */ if (!defined("LESSON_RANDOMPAGE")) { define("LESSON_RANDOMPAGE", -60); } /** * Jump to a random Branch */ if (!defined("LESSON_RANDOMBRANCH")) { define("LESSON_RANDOMBRANCH", -70); } /** * Cluster Jump */ if (!defined("LESSON_CLUSTERJUMP")) { define("LESSON_CLUSTERJUMP", -80); } /** * Undefined */ if (!defined("LESSON_UNDEFINED")) { define("LESSON_UNDEFINED", -99); } // Lesson question types defined /** * Short answer question type */ if (!defined("LESSON_SHORTANSWER")) { define("LESSON_SHORTANSWER", "1"); } /** * True/False question type */ if (!defined("LESSON_TRUEFALSE")) { define("LESSON_TRUEFALSE", "2"); } /** * Multichoice question type * * If you change the value of this then you need * to change it in restorelib.php as well. */ if (!defined("LESSON_MULTICHOICE")) { define("LESSON_MULTICHOICE", "3"); } /** * Random question type - not used */ if (!defined("LESSON_RANDOM")) { define("LESSON_RANDOM", "4"); } /** * Matching question type * * If you change the value of this then you need * to change it in restorelib.php, in mysql.php * and postgres7.php as well. */ if (!defined("LESSON_MATCHING")) { define("LESSON_MATCHING", "5"); } /** * Not sure - not used */ if (!defined("LESSON_RANDOMSAMATCH")) { define("LESSON_RANDOMSAMATCH", "6"); } /** * Not sure - not used */ if (!defined("LESSON_DESCRIPTION")) { define("LESSON_DESCRIPTION", "7"); } /** * Numerical question type */ if (!defined("LESSON_NUMERICAL")) { define("LESSON_NUMERICAL", "8"); } /** * Multichoice with multianswer question type */ if (!defined("LESSON_MULTIANSWER")) { define("LESSON_MULTIANSWER", "9"); } /** * Essay question type */ if (!defined("LESSON_ESSAY")) { define("LESSON_ESSAY", "10"); } /** * Lesson question type array. * Contains all question types used */ $LESSON_QUESTION_TYPE = array ( LESSON_MULTICHOICE => get_string("multichoice", "quiz"), LESSON_TRUEFALSE => get_string("truefalse", "quiz"), LESSON_SHORTANSWER => get_string("shortanswer", "quiz"), LESSON_NUMERICAL => get_string("numerical", "quiz"), LESSON_MATCHING => get_string("match", "quiz"), LESSON_ESSAY => get_string("essay", "lesson") // LESSON_DESCRIPTION => get_string("description", "quiz"), // LESSON_RANDOM => get_string("random", "quiz"), // LESSON_RANDOMSAMATCH => get_string("randomsamatch", "quiz"), // LESSON_MULTIANSWER => get_string("multianswer", "quiz"), ); // Non-question page types /** * Branch Table page */ if (!defined("LESSON_BRANCHTABLE")) { define("LESSON_BRANCHTABLE", "20"); } /** * End of Branch page */ if (!defined("LESSON_ENDOFBRANCH")) { define("LESSON_ENDOFBRANCH", "21"); } /** * Start of Cluster page */ if (!defined("LESSON_CLUSTER")) { define("LESSON_CLUSTER", "30"); } /** * End of Cluster page */ if (!defined("LESSON_ENDOFCLUSTER")) { define("LESSON_ENDOFCLUSTER", "31"); } // other variables... /** * Flag for the editor for the answer textarea. */ if (!defined("LESSON_ANSWER_EDITOR")) { define("LESSON_ANSWER_EDITOR", "1"); } /** * Flag for the editor for the response textarea. */ if (!defined("LESSON_RESPONSE_EDITOR")) { define("LESSON_RESPONSE_EDITOR", "2"); } ////////////////////////////////////////////////////////////////////////////////////// /// Any other lesson functions go here. Each of them must have a name that /// starts with lesson_ /** * Print the standard header for lesson module * * @param object $cm Course module record object * @param object $course Couse record object * @param object $lesson Lesson module record object * @param string $currenttab Current tab for the lesson tabs * @param boolean $printheading Print the a heading with the lesson name * @return void **/ function lesson_print_header($cm, $course, $lesson, $currenttab = '') { global $CFG, $USER; $strlessons = get_string('modulenameplural', 'lesson'); $strlesson = get_string('modulename', 'lesson'); $strname = format_string($lesson->name, true); $context = get_context_instance(CONTEXT_MODULE, $cm->id); // Changed the update_module_button and added another button when a teacher is checking the navigation of the lesson if (has_capability('mod/lesson:edit', $context)) { $button = update_module_button($cm->id, $course->id, $strlesson); if ($currenttab == 'view') { if (!$pageid = optional_param('pageid', 0, PARAM_INT)) { $pageid = get_field('lesson_pages', 'id', 'lessonid', $lesson->id, 'prevpageid', 0); } if (!empty($pageid) and $pageid != LESSON_EOL) { $button = '
'.$button. ''. '
'. ''. ''. ''. ''. '
'; } } } else { $button = ''; } /// Header setup if ($course->category) { $navigation = "wwwroot/course/view.php?id=$course->id\" title=\"$course->fullname\">$course->shortname ->"; } else { $navigation = ''; } /// Print header, heading, tabs and messages print_header("$course->shortname: $strname", $course->fullname, "$navigation id\" title=\"$strlessons\">$strlessons -> $strname", '', '', true, $button, navmenu($course, $cm)); if (has_capability('mod/lesson:manage')) { print_heading_with_help(format_string($lesson->name, true), "overview", "lesson"); } else { print_heading($lesson->name); } if (!empty($currenttab) and has_capability('mod/lesson:manage', $context)) { include($CFG->dirroot.'/mod/lesson/tabs.php'); } lesson_print_messages(); } /** * Returns course module, course and module instance given * either the course module ID or a lesson module ID. * * @param int $cmid Course Module ID * @param int $lessonid Lesson module instance ID * @return array array($cm, $course, $lesson) **/ function lesson_get_basics($cmid = 0, $lessonid = 0) { if ($cmid) { if (!$cm = get_coursemodule_from_id('lesson', $cmid)) { error('Course Module ID was incorrect'); } if (!$course = get_record('course', 'id', $cm->course)) { error('Course is misconfigured'); } if (!$lesson = get_record('lesson', 'id', $cm->instance)) { error('Course module is incorrect'); } } else if ($lessonid) { if (!$lesson = get_record('lesson', 'id', $lessonid)) { error('Course module is incorrect'); } if (!$course = get_record('course', 'id', $lesson->course)) { error('Course is misconfigured'); } if (!$cm = get_coursemodule_from_instance('lesson', $lesson->id, $course->id)) { error('Course Module ID was incorrect'); } } else { error('No course module ID or lesson ID were passed'); } return array($cm, $course, $lesson); } /** * Sets a message to be printed. Messages are printed * by calling {@link lesson_print_messages()}. * * @uses $SESSION * @param string $message The message to be printed * @param string $class Class to be passed to {@link notify()}. Usually notifyproblem or notifysuccess. * @param string $align Alignment of the message * @return boolean **/ function lesson_set_message($message, $class="notifyproblem", $align='center') { global $SESSION; if (empty($SESSION->lesson_messages) or !is_array($SESSION->lesson_messages)) { $SESSION->lesson_messages = array(); } $SESSION->lesson_messages[] = array($message, $class, $align); return true; } /** * Print all set messages. * * See {@link lesson_set_message()} for setting messages. * * Uses {@link notify()} to print the messages. * * @uses $SESSION * @return boolean **/ function lesson_print_messages() { global $SESSION; if (empty($SESSION->lesson_messages)) { // No messages to print return true; } foreach($SESSION->lesson_messages as $message) { notify($message[0], $message[1], $message[2]); } // Reset unset($SESSION->lesson_messages); return true; } /** * Prints a lesson link that submits a form. * * If Javascript is disabled, then a regular submit button is printed * * @return mixed boolean/html **/ function lesson_print_submit_link($name, $form, $align = 'center', $class='standardbutton', $title = '', $id = '', $return = false) { if (!empty($id)) { $id = " id=\"$id\""; } if (empty($title)) { $title = $name; } $output = "
\n"; $output .= " \n"; $output .= "
\n"; if ($return) { return $output; } else { echo $output; return true; } } /** * Prints a time remaining in the following format: H:MM:SS * * @param int $starttime Time when the lesson started * @param int $maxtime Length of the lesson * @param boolean $return Return output switch * @return mixed boolean/string **/ function lesson_print_time_remaining($starttime, $maxtime, $return = false) { // Calculate hours, minutes and seconds $timeleft = $starttime + $maxtime * 60 - time(); $hours = floor($timeleft/3600); $timeleft = $timeleft - ($hours * 3600); $minutes = floor($timeleft/60); $secs = $timeleft - ($minutes * 60); if ($minutes < 10) { $minutes = "0$minutes"; } if ($secs < 10) { $secs = "0$secs"; } $output = array(); $output[] = $hours; $output[] = $minutes; $output[] = $secs; $output = implode(':', $output); if ($return) { return $output; } else { echo $output; return true; } } /** * Given some question info and some data about the the answers * this function parses, organises and saves the question * * This is only used when IMPORTING questions and is only called * from format.php * Lifted from mod/quiz/lib.php - * 1. all reference to oldanswers removed * 2. all reference to quiz_multichoice table removed * 3. In SHORTANSWER questions usecase is store in the qoption field * 4. In NUMERIC questions store the range as two answers * 5. TRUEFALSE options are ignored * 6. For MULTICHOICE questions with more than one answer the qoption field is true * * @param opject $question Contains question data like question, type and answers. * @return object Returns $result->error or $result->notice. **/ function lesson_save_question_options($question) { $timenow = time(); switch ($question->qtype) { case LESSON_SHORTANSWER: $answers = array(); $maxfraction = -1; // Insert all the new answers foreach ($question->answer as $key => $dataanswer) { if ($dataanswer != "") { $answer = new stdClass; $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; if ($question->fraction[$key] >=0.5) { $answer->jumpto = LESSON_NEXTPAGE; } $answer->timecreated = $timenow; $answer->grade = $question->fraction[$key] * 100; $answer->answer = $dataanswer; $answer->response = $question->feedback[$key]; if (!$answer->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert shortanswer quiz answer!"; return $result; } $answers[] = $answer->id; if ($question->fraction[$key] > $maxfraction) { $maxfraction = $question->fraction[$key]; } } } /// Perform sanity checks on fractional grades if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; $result->notice = get_string("fractionsnomax", "quiz", $maxfraction); return $result; } break; case LESSON_NUMERICAL: // Note similarities to SHORTANSWER $answers = array(); $maxfraction = -1; // for each answer store the pair of min and max values even if they are the same foreach ($question->answer as $key => $dataanswer) { if ($dataanswer != "") { $answer = new stdClass; $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; $answer->jumpto = LESSON_NEXTPAGE; $answer->timecreated = $timenow; $answer->grade = $question->fraction[$key] * 100; $min = $question->answer[$key] - $question->tolerance[$key]; $max = $question->answer[$key] + $question->tolerance[$key]; $answer->answer = $min.":".$max; // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max $answer->response = $question->feedback[$key]; if (!$answer->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert numerical quiz answer!"; return $result; } $answers[] = $answer->id; if ($question->fraction[$key] > $maxfraction) { $maxfraction = $question->fraction[$key]; } } } /// Perform sanity checks on fractional grades if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; $result->notice = get_string("fractionsnomax", "quiz", $maxfraction); return $result; } break; case LESSON_TRUEFALSE: // the truth $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; $answer->timecreated = $timenow; $answer->answer = get_string("true", "quiz"); $answer->grade = $question->answer * 100; if ($answer->grade > 50 ) { $answer->jumpto = LESSON_NEXTPAGE; } if (isset($question->feedbacktrue)) { $answer->response = $question->feedbacktrue; } if (!$true->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert quiz answer \"true\")!"; return $result; } // the lie $answer = new stdClass; $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; $answer->timecreated = $timenow; $answer->answer = get_string("false", "quiz"); $answer->grade = (1 - (int)$question->answer) * 100; if ($answer->grade > 50 ) { $answer->jumpto = LESSON_NEXTPAGE; } if (isset($question->feedbackfalse)) { $answer->response = $question->feedbackfalse; } if (!$false->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert quiz answer \"false\")!"; return $result; } break; case LESSON_MULTICHOICE: $totalfraction = 0; $maxfraction = -1; $answers = array(); // Insert all the new answers foreach ($question->answer as $key => $dataanswer) { if ($dataanswer != "") { $answer = new stdClass; $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; $answer->timecreated = $timenow; $answer->grade = $question->fraction[$key] * 100; // changed some defaults /* Original Code if ($answer->grade > 50 ) { $answer->jumpto = LESSON_NEXTPAGE; } Replaced with: */ if ($answer->grade > 50 ) { $answer->jumpto = LESSON_NEXTPAGE; $answer->score = 1; } // end Replace $answer->answer = $dataanswer; $answer->response = $question->feedback[$key]; if (!$answer->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert multichoice quiz answer! "; return $result; } // for Sanity checks if ($question->fraction[$key] > 0) { $totalfraction += $question->fraction[$key]; } if ($question->fraction[$key] > $maxfraction) { $maxfraction = $question->fraction[$key]; } } } /// Perform sanity checks on fractional grades if ($question->single) { if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; $result->notice = get_string("fractionsnomax", "quiz", $maxfraction); return $result; } } else { $totalfraction = round($totalfraction,2); if ($totalfraction != 1) { $totalfraction = $totalfraction * 100; $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction); return $result; } } break; case LESSON_MATCHING: $subquestions = array(); $i = 0; // Insert all the new question+answer pairs foreach ($question->subquestions as $key => $questiontext) { $answertext = $question->subanswers[$key]; if (!empty($questiontext) and !empty($answertext)) { $answer = new stdClass; $answer->lessonid = $question->lessonid; $answer->pageid = $question->id; $answer->timecreated = $timenow; $answer->answer = $questiontext; $answer->response = $answertext; if ($i == 0) { // first answer contains the correct answer jump $answer->jumpto = LESSON_NEXTPAGE; } if (!$subquestion->id = insert_record("lesson_answers", $answer)) { $result->error = "Could not insert quiz match subquestion!"; return $result; } $subquestions[] = $subquestion->id; $i++; } } if (count($subquestions) < 3) { $result->notice = get_string("notenoughsubquestions", "quiz"); return $result; } break; case LESSON_RANDOMSAMATCH: $options->question = $question->id; $options->choose = $question->choose; if ($existing = get_record("quiz_randomsamatch", "question", $options->question)) { $options->id = $existing->id; if (!update_record("quiz_randomsamatch", $options)) { $result->error = "Could not update quiz randomsamatch options!"; return $result; } } else { if (!insert_record("quiz_randomsamatch", $options)) { $result->error = "Could not insert quiz randomsamatch options!"; return $result; } } break; case LESSON_MULTIANSWER: if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) { $oldmultianswers = array(); } // Insert all the new multi answers foreach ($question->answers as $dataanswer) { if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it $multianswer = $oldmultianswer; $multianswer->positionkey = $dataanswer->positionkey; $multianswer->norm = $dataanswer->norm; $multianswer->answertype = $dataanswer->answertype; if (! $multianswer->answers = quiz_save_multianswer_alternatives ($question->id, $dataanswer->answertype, $dataanswer->alternatives, $oldmultianswer->answers)) { $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)"; return $result; } if (!update_record("quiz_multianswers", $multianswer)) { $result->error = "Could not update quiz multianswer! (id=$multianswer->id)"; return $result; } } else { // This is a completely new answer $multianswer = new stdClass; $multianswer->question = $question->id; $multianswer->positionkey = $dataanswer->positionkey; $multianswer->norm = $dataanswer->norm; $multianswer->answertype = $dataanswer->answertype; if (! $multianswer->answers = quiz_save_multianswer_alternatives ($question->id, $dataanswer->answertype, $dataanswer->alternatives)) { $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)"; return $result; } if (!insert_record("quiz_multianswers", $multianswer)) { $result->error = "Could not insert quiz multianswer!"; return $result; } } } break; case LESSON_RANDOM: break; case LESSON_DESCRIPTION: break; default: $result->error = "Unsupported question type ($question->qtype)!"; return $result; break; } return true; } /** * Given an array of value, creates a popup menu to be part of a form. * * @param array $options Used to create the popup menu values ( $options["value"]["label"] ). * @param string $name Name of the select form element. * @param string $selected Current value selected in the popup menu. * @param string $nothing If set, used as the first value in the popup menu. * @param string $script OnChange javascript code. * @param string|int $nothingvalue Value of the $nothing parameter. * @param boolean $return False: Print out the popup menu automatically True: Return the popup menu. * @return string May return the popup menu as a string. * @todo replace the use of this function with choose_from_menu in lib/weblib.php **/ function lesson_choose_from_menu ($options, $name, $selected="", $nothing="choose", $script="", $nothingvalue="0", $return=false) { if ($nothing == "choose") { $nothing = get_string("choose")."..."; } if ($script) { $javascript = "onChange=\"$script\""; } else { $javascript = ""; } $output = " \n"; } /** * Checks to see if the nickname is naughty or not. * * @todo Move this to highscores.php */ function lesson_check_nickname($name) { if (empty($name)) { return false; } $filterwords = explode(',', get_string('censorbadwords')); foreach ($filterwords as $filterword) { if (strstr($name, $filterword)) { return false; } } return true; } /** * Prints out a Progress Bar which depicts a user's progress within a lesson. * * Currently works best with a linear lesson. Clusters are counted as a single page. * Also, only viewed branch tables and questions that have been answered correctly count * toward lesson completion (or progress). Only Students can see the Progress bar as well. * * @param object $lesson The lesson that the user is currently taking. * @param object $course The course that the to which the lesson belongs. * @return boolean The return is not significant as of yet. Will return true/false. * @author Mark Nielsen **/ function lesson_print_progress_bar($lesson, $course) { global $CFG, $USER; $cm = get_coursemodule_from_instance('lesson', $lesson->id); $context = get_context_instance(CONTEXT_MODULE, $cm->id); // lesson setting to turn progress bar on or off if (!$lesson->progressbar) { return false; } // catch teachers if (has_capability('mod/lesson:manage', $context)) { notify(get_string('progressbarteacherwarning', 'lesson', $course->teachers)); return false; } if (!isset($USER->modattempts[$lesson->id])) { // all of the lesson pages if (!$pages = get_records('lesson_pages', 'lessonid', $lesson->id)) { return false; } else { foreach ($pages as $page) { if ($page->prevpageid == 0) { $pageid = $page->id; // find the first page id break; } } } // current attempt number if (!$ntries = count_records("lesson_grades", "lessonid", $lesson->id, "userid", $USER->id)) { $ntries = 0; // may not be necessary } $viewedpageids = array(); // collect all of the correctly answered questions if ($viewedpages = get_records_select("lesson_attempts", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries AND correct = 1", 'timeseen DESC', 'pageid, id')) { $viewedpageids = array_keys($viewedpages); } // collect all of the branch tables viewed if ($viewedbranches = get_records_select("lesson_branch", "lessonid = $lesson->id AND userid = $USER->id AND retry = $ntries", 'timeseen DESC', 'pageid, id')) { $viewedpageids = array_merge($viewedpageids, array_keys($viewedbranches)); } // Filter out the following pages: // End of Cluster // End of Branch // Pages found inside of Clusters // Do not filter out Cluster Page(s) because we count a cluster as one. // By keeping the cluster page, we get our 1 $validpages = array(); while ($pageid != 0) { if ($pages[$pageid]->qtype == LESSON_CLUSTER) { $clusterpageid = $pageid; // copy it $validpages[$clusterpageid] = 1; // add the cluster page as a valid page $pageid = $pages[$pageid]->nextpageid; // get next page // now, remove all necessary viewed paged ids from the viewedpageids array. while ($pages[$pageid]->qtype != LESSON_ENDOFCLUSTER and $pageid != 0) { if (in_array($pageid, $viewedpageids)) { unset($viewedpageids[array_search($pageid, $viewedpageids)]); // remove it // since the user did see one page in the cluster, add the cluster pageid to the viewedpageids if (!in_array($clusterpageid, $viewedpageids)) { $viewedpageids[] = $clusterpageid; } } $pageid = $pages[$pageid]->nextpageid; } } elseif ($pages[$pageid]->qtype == LESSON_ENDOFCLUSTER or $pages[$pageid]->qtype == LESSON_ENDOFBRANCH) { // dont count these, just go to next $pageid = $pages[$pageid]->nextpageid; } else { // a counted page $validpages[$pageid] = 1; $pageid = $pages[$pageid]->nextpageid; } } // progress calculation as a percent $progress = round(count($viewedpageids)/count($validpages), 2) * 100; } else { $progress = 100; } // print out the Progress Bar. Attempted to put as much as possible in the style sheets. echo '
'; echo ''; if ($progress != 0) { // some browsers do not repsect the 0 width. echo ''; } echo ''; echo '
'; echo ''; echo '
'; echo '
'; echo '
'; return true; } /** * Determines if a user can view the left menu. The determining factor * is whether a user has a grade greater than or equal to the lesson setting * of displayleftif * * @param object $lesson Lesson object of the current lesson * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged * @author Mark Nielsen **/ function lesson_displayleftif($lesson) { global $CFG, $USER; if (!empty($lesson->displayleftif)) { // get the current user's max grade for this lesson if ($maxgrade = get_record_sql('SELECT userid, MAX(grade) AS maxgrade FROM '.$CFG->prefix.'lesson_grades WHERE userid = '.$USER->id.' AND lessonid = '.$lesson->id.' GROUP BY userid')) { if ($maxgrade->maxgrade < $lesson->displayleftif) { return 0; // turn off the displayleft } } else { return 0; // no grades } } // if we get to here, keep the original state of displayleft lesson setting return $lesson->displayleft; } ?>