Commit | Line | Data |
---|---|---|
87f83794 | 1 | <?php |
2 | ||
0a4abb73 SH |
3 | // This file is part of Moodle - http://moodle.org/ |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
5491947a | 18 | /** |
19 | * Local library file for Lesson. These are non-standard functions that are used | |
20 | * only by Lesson. | |
21 | * | |
9b24f68b | 22 | * @package mod_lesson |
0a4abb73 | 23 | * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} |
cc3dbaaa | 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late |
5491947a | 25 | **/ |
4b55d2af | 26 | |
0a4abb73 | 27 | /** Make sure this isn't being directly accessed */ |
1e7f8ea2 | 28 | defined('MOODLE_INTERNAL') || die(); |
0a4abb73 SH |
29 | |
30 | /** Include the files that are required by this module */ | |
1e7f8ea2 | 31 | require_once($CFG->dirroot.'/course/moodleform_mod.php'); |
0a4abb73 | 32 | require_once($CFG->dirroot . '/mod/lesson/lib.php'); |
99d19c13 | 33 | require_once($CFG->libdir . '/filelib.php'); |
0a4abb73 | 34 | |
44cb7e63 PS |
35 | /** This page */ |
36 | define('LESSON_THISPAGE', 0); | |
0a4abb73 SH |
37 | /** Next page -> any page not seen before */ |
38 | define("LESSON_UNSEENPAGE", 1); | |
39 | /** Next page -> any page not answered correctly */ | |
40 | define("LESSON_UNANSWEREDPAGE", 2); | |
41 | /** Jump to Next Page */ | |
42 | define("LESSON_NEXTPAGE", -1); | |
43 | /** End of Lesson */ | |
44 | define("LESSON_EOL", -9); | |
45 | /** Jump to an unseen page within a branch and end of branch or end of lesson */ | |
46 | define("LESSON_UNSEENBRANCHPAGE", -50); | |
47 | /** Jump to Previous Page */ | |
48 | define("LESSON_PREVIOUSPAGE", -40); | |
49 | /** Jump to a random page within a branch and end of branch or end of lesson */ | |
50 | define("LESSON_RANDOMPAGE", -60); | |
51 | /** Jump to a random Branch */ | |
52 | define("LESSON_RANDOMBRANCH", -70); | |
53 | /** Cluster Jump */ | |
54 | define("LESSON_CLUSTERJUMP", -80); | |
55 | /** Undefined */ | |
56 | define("LESSON_UNDEFINED", -99); | |
5e7856af | 57 | |
1e7f8ea2 PS |
58 | /** LESSON_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */ |
59 | define("LESSON_MAX_EVENT_LENGTH", "432000"); | |
60 | ||
a1556aaf SB |
61 | /** Answer format is HTML */ |
62 | define("LESSON_ANSWER_HTML", "HTML"); | |
1e7f8ea2 | 63 | |
8a8bd425 CB |
64 | // Event types. |
65 | define('LESSON_EVENT_TYPE_OPEN', 'open'); | |
66 | define('LESSON_EVENT_TYPE_CLOSE', 'close'); | |
67 | ||
5e7856af | 68 | ////////////////////////////////////////////////////////////////////////////////////// |
86342d63 | 69 | /// Any other lesson functions go here. Each of them must have a name that |
5e7856af | 70 | /// starts with lesson_ |
71 | ||
4b55d2af | 72 | /** |
86342d63 | 73 | * Checks to see if a LESSON_CLUSTERJUMP or |
4b55d2af | 74 | * a LESSON_UNSEENBRANCHPAGE is used in a lesson. |
75 | * | |
86342d63 | 76 | * This function is only executed when a teacher is |
4b55d2af | 77 | * checking the navigation for a lesson. |
78 | * | |
3983f2dc | 79 | * @param stdClass $lesson Id of the lesson that is to be checked. |
4b55d2af | 80 | * @return boolean True or false. |
81 | **/ | |
06469639 | 82 | function lesson_display_teacher_warning($lesson) { |
646fc290 | 83 | global $DB; |
86342d63 | 84 | |
ac8e16be | 85 | // get all of the lesson answers |
0a4abb73 | 86 | $params = array ("lessonid" => $lesson->id); |
646fc290 | 87 | if (!$lessonanswers = $DB->get_records_select("lesson_answers", "lessonid = :lessonid", $params)) { |
64a3ce8c | 88 | // no answers, then not using cluster or unseen |
ac8e16be | 89 | return false; |
90 | } | |
91 | // just check for the first one that fulfills the requirements | |
92 | foreach ($lessonanswers as $lessonanswer) { | |
93 | if ($lessonanswer->jumpto == LESSON_CLUSTERJUMP || $lessonanswer->jumpto == LESSON_UNSEENBRANCHPAGE) { | |
94 | return true; | |
95 | } | |
96 | } | |
86342d63 | 97 | |
ac8e16be | 98 | // if no answers use either of the two jumps |
99 | return false; | |
5e7856af | 100 | } |
101 | ||
4b55d2af | 102 | /** |
103 | * Interprets the LESSON_UNSEENBRANCHPAGE jump. | |
86342d63 | 104 | * |
4b55d2af | 105 | * will return the pageid of a random unseen page that is within a branch |
106 | * | |
0a4abb73 | 107 | * @param lesson $lesson |
f521f98a | 108 | * @param int $userid Id of the user. |
4b55d2af | 109 | * @param int $pageid Id of the page from which we are jumping. |
110 | * @return int Id of the next page. | |
4b55d2af | 111 | **/ |
5e7856af | 112 | function lesson_unseen_question_jump($lesson, $user, $pageid) { |
646fc290 | 113 | global $DB; |
86342d63 | 114 | |
ac8e16be | 115 | // get the number of retakes |
0a4abb73 | 116 | if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$user))) { |
ac8e16be | 117 | $retakes = 0; |
118 | } | |
119 | ||
120 | // get all the lesson_attempts aka what the user has seen | |
0a4abb73 | 121 | if ($viewedpages = $DB->get_records("lesson_attempts", array("lessonid"=>$lesson->id, "userid"=>$user, "retry"=>$retakes), "timeseen DESC")) { |
ac8e16be | 122 | foreach($viewedpages as $viewed) { |
123 | $seenpages[] = $viewed->pageid; | |
124 | } | |
125 | } else { | |
126 | $seenpages = array(); | |
127 | } | |
128 | ||
129 | // get the lesson pages | |
0a4abb73 | 130 | $lessonpages = $lesson->load_all_pages(); |
86342d63 | 131 | |
ac8e16be | 132 | if ($pageid == LESSON_UNSEENBRANCHPAGE) { // this only happens when a student leaves in the middle of an unseen question within a branch series |
133 | $pageid = $seenpages[0]; // just change the pageid to the last page viewed inside the branch table | |
134 | } | |
135 | ||
136 | // go up the pages till branch table | |
137 | while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page | |
0a4abb73 | 138 | if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) { |
ac8e16be | 139 | break; |
140 | } | |
141 | $pageid = $lessonpages[$pageid]->prevpageid; | |
142 | } | |
86342d63 | 143 | |
97b4ec5e | 144 | $pagesinbranch = $lesson->get_sub_pages_of($pageid, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH)); |
86342d63 | 145 | |
ac8e16be | 146 | // this foreach loop stores all the pages that are within the branch table but are not in the $seenpages array |
147 | $unseen = array(); | |
86342d63 | 148 | foreach($pagesinbranch as $page) { |
ac8e16be | 149 | if (!in_array($page->id, $seenpages)) { |
150 | $unseen[] = $page->id; | |
151 | } | |
152 | } | |
153 | ||
154 | if(count($unseen) == 0) { | |
155 | if(isset($pagesinbranch)) { | |
156 | $temp = end($pagesinbranch); | |
157 | $nextpage = $temp->nextpageid; // they have seen all the pages in the branch, so go to EOB/next branch table/EOL | |
158 | } else { | |
159 | // there are no pages inside the branch, so return the next page | |
160 | $nextpage = $lessonpages[$pageid]->nextpageid; | |
161 | } | |
162 | if ($nextpage == 0) { | |
163 | return LESSON_EOL; | |
164 | } else { | |
165 | return $nextpage; | |
166 | } | |
167 | } else { | |
168 | return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page | |
169 | } | |
5e7856af | 170 | } |
171 | ||
4b55d2af | 172 | /** |
173 | * Handles the unseen branch table jump. | |
174 | * | |
0a4abb73 | 175 | * @param lesson $lesson |
f521f98a | 176 | * @param int $userid User id. |
4b55d2af | 177 | * @return int Will return the page id of a branch table or end of lesson |
4b55d2af | 178 | **/ |
0a4abb73 | 179 | function lesson_unseen_branch_jump($lesson, $userid) { |
646fc290 | 180 | global $DB; |
86342d63 | 181 | |
0a4abb73 | 182 | if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$userid))) { |
ac8e16be | 183 | $retakes = 0; |
184 | } | |
185 | ||
b584c49d | 186 | if (!$seenbranches = $lesson->get_content_pages_viewed($retakes, $userid, 'timeseen DESC')) { |
86f93345 | 187 | print_error('cannotfindrecords', 'lesson'); |
ac8e16be | 188 | } |
189 | ||
190 | // get the lesson pages | |
0a4abb73 | 191 | $lessonpages = $lesson->load_all_pages(); |
86342d63 | 192 | |
ff85f902 | 193 | // this loads all the viewed branch tables into $seen until it finds the branch table with the flag |
ac8e16be | 194 | // which is the branch table that starts the unseenbranch function |
86342d63 | 195 | $seen = array(); |
ac8e16be | 196 | foreach ($seenbranches as $seenbranch) { |
197 | if (!$seenbranch->flag) { | |
198 | $seen[$seenbranch->pageid] = $seenbranch->pageid; | |
199 | } else { | |
200 | $start = $seenbranch->pageid; | |
201 | break; | |
202 | } | |
203 | } | |
204 | // this function searches through the lesson pages to find all the branch tables | |
205 | // that follow the flagged branch table | |
206 | $pageid = $lessonpages[$start]->nextpageid; // move down from the flagged branch table | |
0f6e2f02 | 207 | $branchtables = array(); |
ac8e16be | 208 | while ($pageid != 0) { // grab all of the branch table till eol |
0a4abb73 | 209 | if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) { |
ac8e16be | 210 | $branchtables[] = $lessonpages[$pageid]->id; |
211 | } | |
212 | $pageid = $lessonpages[$pageid]->nextpageid; | |
213 | } | |
214 | $unseen = array(); | |
215 | foreach ($branchtables as $branchtable) { | |
216 | // load all of the unseen branch tables into unseen | |
217 | if (!array_key_exists($branchtable, $seen)) { | |
218 | $unseen[] = $branchtable; | |
219 | } | |
220 | } | |
221 | if (count($unseen) > 0) { | |
222 | return $unseen[rand(0, count($unseen)-1)]; // returns a random page id for the next page | |
223 | } else { | |
224 | return LESSON_EOL; // has viewed all of the branch tables | |
225 | } | |
5e7856af | 226 | } |
227 | ||
4b55d2af | 228 | /** |
229 | * Handles the random jump between a branch table and end of branch or end of lesson (LESSON_RANDOMPAGE). | |
86342d63 | 230 | * |
0a4abb73 | 231 | * @param lesson $lesson |
4b55d2af | 232 | * @param int $pageid The id of the page that we are jumping from (?) |
233 | * @return int The pageid of a random page that is within a branch table | |
4b55d2af | 234 | **/ |
0a4abb73 | 235 | function lesson_random_question_jump($lesson, $pageid) { |
646fc290 | 236 | global $DB; |
86342d63 | 237 | |
ac8e16be | 238 | // get the lesson pages |
0a4abb73 | 239 | $params = array ("lessonid" => $lesson->id); |
646fc290 | 240 | if (!$lessonpages = $DB->get_records_select("lesson_pages", "lessonid = :lessonid", $params)) { |
86f93345 | 241 | print_error('cannotfindpages', 'lesson'); |
ac8e16be | 242 | } |
243 | ||
244 | // go up the pages till branch table | |
245 | while ($pageid != 0) { // this condition should never be satisfied... only happens if there are no branch tables above this page | |
246 | ||
0a4abb73 | 247 | if ($lessonpages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE) { |
ac8e16be | 248 | break; |
249 | } | |
250 | $pageid = $lessonpages[$pageid]->prevpageid; | |
251 | } | |
252 | ||
86342d63 | 253 | // get the pages within the branch |
97b4ec5e | 254 | $pagesinbranch = $lesson->get_sub_pages_of($pageid, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH)); |
86342d63 | 255 | |
ac8e16be | 256 | if(count($pagesinbranch) == 0) { |
257 | // there are no pages inside the branch, so return the next page | |
258 | return $lessonpages[$pageid]->nextpageid; | |
259 | } else { | |
260 | return $pagesinbranch[rand(0, count($pagesinbranch)-1)]->id; // returns a random page id for the next page | |
261 | } | |
5e7856af | 262 | } |
263 | ||
4b55d2af | 264 | /** |
265 | * Calculates a user's grade for a lesson. | |
266 | * | |
4b55d2af | 267 | * @param object $lesson The lesson that the user is taking. |
4b55d2af | 268 | * @param int $retries The attempt number. |
ff85f902 | 269 | * @param int $userid Id of the user (optional, default current user). |
88427c07 | 270 | * @return object { nquestions => number of questions answered |
271 | attempts => number of question attempts | |
272 | total => max points possible | |
273 | earned => points earned by student | |
274 | grade => calculated percentage grade | |
275 | nmanual => number of manually graded questions | |
276 | manualpoints => point value for manually graded questions } | |
4b55d2af | 277 | */ |
86342d63 | 278 | function lesson_grade($lesson, $ntries, $userid = 0) { |
646fc290 | 279 | global $USER, $DB; |
ac8e16be | 280 | |
88427c07 | 281 | if (empty($userid)) { |
282 | $userid = $USER->id; | |
283 | } | |
86342d63 | 284 | |
88427c07 | 285 | // Zero out everything |
286 | $ncorrect = 0; | |
287 | $nviewed = 0; | |
288 | $score = 0; | |
289 | $nmanual = 0; | |
290 | $manualpoints = 0; | |
291 | $thegrade = 0; | |
292 | $nquestions = 0; | |
293 | $total = 0; | |
294 | $earned = 0; | |
295 | ||
646fc290 | 296 | $params = array ("lessonid" => $lesson->id, "userid" => $userid, "retry" => $ntries); |
86342d63 | 297 | if ($useranswers = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND |
646fc290 | 298 | userid = :userid AND retry = :retry", $params, "timeseen")) { |
88427c07 | 299 | // group each try with its page |
300 | $attemptset = array(); | |
301 | foreach ($useranswers as $useranswer) { | |
86342d63 | 302 | $attemptset[$useranswer->pageid][] = $useranswer; |
ac8e16be | 303 | } |
86342d63 | 304 | |
88427c07 | 305 | // Drop all attempts that go beyond max attempts for the lesson |
306 | foreach ($attemptset as $key => $set) { | |
307 | $attemptset[$key] = array_slice($set, 0, $lesson->maxattempts); | |
308 | } | |
86342d63 | 309 | |
88427c07 | 310 | // get only the pages and their answers that the user answered |
646fc290 | 311 | list($usql, $parameters) = $DB->get_in_or_equal(array_keys($attemptset)); |
0a4abb73 SH |
312 | array_unshift($parameters, $lesson->id); |
313 | $pages = $DB->get_records_select("lesson_pages", "lessonid = ? AND id $usql", $parameters); | |
314 | $answers = $DB->get_records_select("lesson_answers", "lessonid = ? AND pageid $usql", $parameters); | |
86342d63 | 315 | |
88427c07 | 316 | // Number of pages answered |
317 | $nquestions = count($pages); | |
318 | ||
319 | foreach ($attemptset as $attempts) { | |
0a4abb73 | 320 | $page = lesson_page::load($pages[end($attempts)->pageid], $lesson); |
88427c07 | 321 | if ($lesson->custom) { |
322 | $attempt = end($attempts); | |
323 | // If essay question, handle it, otherwise add to score | |
0a4abb73 | 324 | if ($page->requires_manual_grading()) { |
f672e3e9 RW |
325 | $useranswerobj = unserialize($attempt->useranswer); |
326 | if (isset($useranswerobj->score)) { | |
327 | $earned += $useranswerobj->score; | |
328 | } | |
88427c07 | 329 | $nmanual++; |
330 | $manualpoints += $answers[$attempt->answerid]->score; | |
ab1e7c39 | 331 | } else if (!empty($attempt->answerid)) { |
0a4abb73 | 332 | $earned += $page->earned_score($answers, $attempt); |
88427c07 | 333 | } |
334 | } else { | |
335 | foreach ($attempts as $attempt) { | |
336 | $earned += $attempt->correct; | |
337 | } | |
338 | $attempt = end($attempts); // doesn't matter which one | |
339 | // If essay question, increase numbers | |
0a4abb73 | 340 | if ($page->requires_manual_grading()) { |
88427c07 | 341 | $nmanual++; |
342 | $manualpoints++; | |
ac8e16be | 343 | } |
344 | } | |
88427c07 | 345 | // Number of times answered |
346 | $nviewed += count($attempts); | |
347 | } | |
86342d63 | 348 | |
88427c07 | 349 | if ($lesson->custom) { |
ac8e16be | 350 | $bestscores = array(); |
88427c07 | 351 | // Find the highest possible score per page to get our total |
352 | foreach ($answers as $answer) { | |
46341ab7 | 353 | if(!isset($bestscores[$answer->pageid])) { |
88427c07 | 354 | $bestscores[$answer->pageid] = $answer->score; |
46341ab7 | 355 | } else if ($bestscores[$answer->pageid] < $answer->score) { |
88427c07 | 356 | $bestscores[$answer->pageid] = $answer->score; |
ac8e16be | 357 | } |
358 | } | |
88427c07 | 359 | $total = array_sum($bestscores); |
360 | } else { | |
361 | // Check to make sure the student has answered the minimum questions | |
362 | if ($lesson->minquestions and $nquestions < $lesson->minquestions) { | |
363 | // Nope, increase number viewed by the amount of unanswered questions | |
364 | $total = $nviewed + ($lesson->minquestions - $nquestions); | |
365 | } else { | |
366 | $total = $nviewed; | |
367 | } | |
ac8e16be | 368 | } |
88427c07 | 369 | } |
86342d63 | 370 | |
88427c07 | 371 | if ($total) { // not zero |
372 | $thegrade = round(100 * $earned / $total, 5); | |
373 | } | |
86342d63 | 374 | |
88427c07 | 375 | // Build the grade information object |
376 | $gradeinfo = new stdClass; | |
377 | $gradeinfo->nquestions = $nquestions; | |
378 | $gradeinfo->attempts = $nviewed; | |
379 | $gradeinfo->total = $total; | |
380 | $gradeinfo->earned = $earned; | |
381 | $gradeinfo->grade = $thegrade; | |
382 | $gradeinfo->nmanual = $nmanual; | |
383 | $gradeinfo->manualpoints = $manualpoints; | |
86342d63 | 384 | |
88427c07 | 385 | return $gradeinfo; |
386 | } | |
387 | ||
62eda6ea | 388 | /** |
389 | * Determines if a user can view the left menu. The determining factor | |
390 | * is whether a user has a grade greater than or equal to the lesson setting | |
391 | * of displayleftif | |
392 | * | |
393 | * @param object $lesson Lesson object of the current lesson | |
394 | * @return boolean 0 if the user cannot see, or $lesson->displayleft to keep displayleft unchanged | |
62eda6ea | 395 | **/ |
396 | function lesson_displayleftif($lesson) { | |
646fc290 | 397 | global $CFG, $USER, $DB; |
86342d63 | 398 | |
62eda6ea | 399 | if (!empty($lesson->displayleftif)) { |
400 | // get the current user's max grade for this lesson | |
646fc290 | 401 | $params = array ("userid" => $USER->id, "lessonid" => $lesson->id); |
402 | 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)) { | |
62eda6ea | 403 | if ($maxgrade->maxgrade < $lesson->displayleftif) { |
404 | return 0; // turn off the displayleft | |
405 | } | |
406 | } else { | |
407 | return 0; // no grades | |
408 | } | |
409 | } | |
86342d63 | 410 | |
62eda6ea | 411 | // if we get to here, keep the original state of displayleft lesson setting |
412 | return $lesson->displayleft; | |
413 | } | |
5e7856af | 414 | |
4262a2f8 | 415 | /** |
86342d63 | 416 | * |
4262a2f8 | 417 | * @param $cm |
418 | * @param $lesson | |
419 | * @param $page | |
420 | * @return unknown_type | |
421 | */ | |
d9c26e21 | 422 | function lesson_add_fake_blocks($page, $cm, $lesson, $timer = null) { |
4262a2f8 | 423 | $bc = lesson_menu_block_contents($cm->id, $lesson); |
424 | if (!empty($bc)) { | |
425 | $regions = $page->blocks->get_regions(); | |
426 | $firstregion = reset($regions); | |
d9c26e21 | 427 | $page->blocks->add_fake_block($bc, $firstregion); |
4262a2f8 | 428 | } |
429 | ||
430 | $bc = lesson_mediafile_block_contents($cm->id, $lesson); | |
431 | if (!empty($bc)) { | |
d9c26e21 | 432 | $page->blocks->add_fake_block($bc, $page->blocks->get_default_region()); |
4262a2f8 | 433 | } |
434 | ||
435 | if (!empty($timer)) { | |
436 | $bc = lesson_clock_block_contents($cm->id, $lesson, $timer, $page); | |
437 | if (!empty($bc)) { | |
d9c26e21 | 438 | $page->blocks->add_fake_block($bc, $page->blocks->get_default_region()); |
4262a2f8 | 439 | } |
440 | } | |
441 | } | |
442 | ||
f521f98a | 443 | /** |
86342d63 | 444 | * If there is a media file associated with this |
4262a2f8 | 445 | * lesson, return a block_contents that displays it. |
f521f98a | 446 | * |
447 | * @param int $cmid Course Module ID for this lesson | |
448 | * @param object $lesson Full lesson record object | |
4262a2f8 | 449 | * @return block_contents |
f521f98a | 450 | **/ |
4262a2f8 | 451 | function lesson_mediafile_block_contents($cmid, $lesson) { |
d68ccdba | 452 | global $OUTPUT; |
8d1a3963 | 453 | if (empty($lesson->mediafile)) { |
4262a2f8 | 454 | return null; |
f521f98a | 455 | } |
4262a2f8 | 456 | |
0a4abb73 SH |
457 | $options = array(); |
458 | $options['menubar'] = 0; | |
459 | $options['location'] = 0; | |
460 | $options['left'] = 5; | |
461 | $options['top'] = 5; | |
462 | $options['scrollbars'] = 1; | |
463 | $options['resizable'] = 1; | |
464 | $options['width'] = $lesson->mediawidth; | |
465 | $options['height'] = $lesson->mediaheight; | |
4262a2f8 | 466 | |
9bf16314 PS |
467 | $link = new moodle_url('/mod/lesson/mediafile.php?id='.$cmid); |
468 | $action = new popup_action('click', $link, 'lessonmediafile', $options); | |
469 | $content = $OUTPUT->action_link($link, get_string('mediafilepopup', 'lesson'), $action, array('title'=>get_string('mediafilepopup', 'lesson'))); | |
86342d63 | 470 | |
4262a2f8 | 471 | $bc = new block_contents(); |
472 | $bc->title = get_string('linkedmedia', 'lesson'); | |
ca332713 | 473 | $bc->attributes['class'] = 'mediafile block'; |
4262a2f8 | 474 | $bc->content = $content; |
475 | ||
476 | return $bc; | |
f521f98a | 477 | } |
478 | ||
479 | /** | |
480 | * If a timed lesson and not a teacher, then | |
4262a2f8 | 481 | * return a block_contents containing the clock. |
f521f98a | 482 | * |
483 | * @param int $cmid Course Module ID for this lesson | |
484 | * @param object $lesson Full lesson record object | |
485 | * @param object $timer Full timer record object | |
4262a2f8 | 486 | * @return block_contents |
f521f98a | 487 | **/ |
4262a2f8 | 488 | function lesson_clock_block_contents($cmid, $lesson, $timer, $page) { |
489 | // Display for timed lessons and for students only | |
5918e371 | 490 | $context = context_module::instance($cmid); |
a1acc001 | 491 | if ($lesson->timelimit == 0 || has_capability('mod/lesson:manage', $context)) { |
4262a2f8 | 492 | return null; |
493 | } | |
f521f98a | 494 | |
50d70c5c | 495 | $content = '<div id="lesson-timer">'; |
0a4abb73 | 496 | $content .= $lesson->time_remaining($timer->starttime); |
4262a2f8 | 497 | $content .= '</div>'; |
ba458143 | 498 | |
a1acc001 | 499 | $clocksettings = array('starttime' => $timer->starttime, 'servertime' => time(), 'testlength' => $lesson->timelimit); |
50d70c5c JMV |
500 | $page->requires->data_for_js('clocksettings', $clocksettings, true); |
501 | $page->requires->strings_for_js(array('timeisup'), 'lesson'); | |
227255b8 | 502 | $page->requires->js('/mod/lesson/timer.js'); |
50d70c5c | 503 | $page->requires->js_init_call('show_clock'); |
ba458143 | 504 | |
4262a2f8 | 505 | $bc = new block_contents(); |
506 | $bc->title = get_string('timeremaining', 'lesson'); | |
6605ff8c | 507 | $bc->attributes['class'] = 'clock block'; |
4262a2f8 | 508 | $bc->content = $content; |
509 | ||
510 | return $bc; | |
f521f98a | 511 | } |
512 | ||
513 | /** | |
514 | * If left menu is turned on, then this will | |
515 | * print the menu in a block | |
516 | * | |
517 | * @param int $cmid Course Module ID for this lesson | |
0a4abb73 | 518 | * @param lesson $lesson Full lesson record object |
f521f98a | 519 | * @return void |
520 | **/ | |
4262a2f8 | 521 | function lesson_menu_block_contents($cmid, $lesson) { |
646fc290 | 522 | global $CFG, $DB; |
f521f98a | 523 | |
4262a2f8 | 524 | if (!$lesson->displayleft) { |
525 | return null; | |
526 | } | |
f521f98a | 527 | |
0a4abb73 SH |
528 | $pages = $lesson->load_all_pages(); |
529 | foreach ($pages as $page) { | |
530 | if ((int)$page->prevpageid === 0) { | |
531 | $pageid = $page->id; | |
532 | break; | |
533 | } | |
534 | } | |
4262a2f8 | 535 | $currentpageid = optional_param('pageid', $pageid, PARAM_INT); |
f521f98a | 536 | |
4262a2f8 | 537 | if (!$pageid || !$pages) { |
538 | return null; | |
f521f98a | 539 | } |
f521f98a | 540 | |
e584e6ae DW |
541 | $content = '<a href="#maincontent" class="accesshide">' . |
542 | get_string('skip', 'lesson') . | |
543 | "</a>\n<div class=\"menuwrapper\">\n<ul>\n"; | |
888f0c54 | 544 | |
4262a2f8 | 545 | while ($pageid != 0) { |
546 | $page = $pages[$pageid]; | |
547 | ||
548 | // Only process branch tables with display turned on | |
0a4abb73 | 549 | if ($page->displayinmenublock && $page->display) { |
86342d63 | 550 | if ($page->id == $currentpageid) { |
4262a2f8 | 551 | $content .= '<li class="selected">'.format_string($page->title,true)."</li>\n"; |
552 | } else { | |
553 | $content .= "<li class=\"notselected\"><a href=\"$CFG->wwwroot/mod/lesson/view.php?id=$cmid&pageid=$page->id\">".format_string($page->title,true)."</a></li>\n"; | |
554 | } | |
86342d63 | 555 | |
888f0c54 | 556 | } |
4262a2f8 | 557 | $pageid = $page->nextpageid; |
888f0c54 | 558 | } |
4262a2f8 | 559 | $content .= "</ul>\n</div>\n"; |
888f0c54 | 560 | |
4262a2f8 | 561 | $bc = new block_contents(); |
562 | $bc->title = get_string('lessonmenu', 'lesson'); | |
6605ff8c | 563 | $bc->attributes['class'] = 'menu block'; |
4262a2f8 | 564 | $bc->content = $content; |
888f0c54 | 565 | |
4262a2f8 | 566 | return $bc; |
448052a5 SH |
567 | } |
568 | ||
569 | /** | |
570 | * Adds header buttons to the page for the lesson | |
571 | * | |
572 | * @param object $cm | |
573 | * @param object $context | |
574 | * @param bool $extraeditbuttons | |
575 | * @param int $lessonpageid | |
576 | */ | |
577 | function lesson_add_header_buttons($cm, $context, $extraeditbuttons=false, $lessonpageid=null) { | |
578 | global $CFG, $PAGE, $OUTPUT; | |
92059c7e SH |
579 | if (has_capability('mod/lesson:edit', $context) && $extraeditbuttons) { |
580 | if ($lessonpageid === null) { | |
581 | print_error('invalidpageid', 'lesson'); | |
582 | } | |
583 | if (!empty($lessonpageid) && $lessonpageid != LESSON_EOL) { | |
32495414 MA |
584 | $url = new moodle_url('/mod/lesson/editpage.php', array( |
585 | 'id' => $cm->id, | |
586 | 'pageid' => $lessonpageid, | |
587 | 'edit' => 1, | |
588 | 'returnto' => $PAGE->url->out(false) | |
589 | )); | |
5c2ed7e2 | 590 | $PAGE->set_button($OUTPUT->single_button($url, get_string('editpagecontent', 'lesson'))); |
448052a5 | 591 | } |
448052a5 | 592 | } |
5c2ed7e2 | 593 | } |
9b56a34f PS |
594 | |
595 | /** | |
596 | * This is a function used to detect media types and generate html code. | |
597 | * | |
598 | * @global object $CFG | |
599 | * @global object $PAGE | |
600 | * @param object $lesson | |
601 | * @param object $context | |
602 | * @return string $code the html code of media | |
603 | */ | |
604 | function lesson_get_media_html($lesson, $context) { | |
605 | global $CFG, $PAGE, $OUTPUT; | |
606 | require_once("$CFG->libdir/resourcelib.php"); | |
607 | ||
64f93798 | 608 | // get the media file link |
8d1a3963 PS |
609 | if (strpos($lesson->mediafile, '://') !== false) { |
610 | $url = new moodle_url($lesson->mediafile); | |
611 | } else { | |
612 | // the timemodified is used to prevent caching problems, instead of '/' we should better read from files table and use sortorder | |
613 | $url = moodle_url::make_pluginfile_url($context->id, 'mod_lesson', 'mediafile', $lesson->timemodified, '/', ltrim($lesson->mediafile, '/')); | |
614 | } | |
9b56a34f PS |
615 | $title = $lesson->mediafile; |
616 | ||
8d1a3963 | 617 | $clicktoopen = html_writer::link($url, get_string('download')); |
9b56a34f PS |
618 | |
619 | $mimetype = resourcelib_guess_url_mimetype($url); | |
620 | ||
fcd2cbaf PS |
621 | $extension = resourcelib_get_extension($url->out(false)); |
622 | ||
1abd4376 | 623 | $mediamanager = core_media_manager::instance($PAGE); |
8b7d95b6 | 624 | $embedoptions = array( |
fab11235 MG |
625 | core_media_manager::OPTION_TRUSTED => true, |
626 | core_media_manager::OPTION_BLOCK => true | |
8b7d95b6 | 627 | ); |
628 | ||
9b56a34f PS |
629 | // find the correct type and print it out |
630 | if (in_array($mimetype, array('image/gif','image/jpeg','image/png'))) { // It's an image | |
631 | $code = resourcelib_embed_image($url, $title); | |
632 | ||
fab11235 | 633 | } else if ($mediamanager->can_embed_url($url, $embedoptions)) { |
8b7d95b6 | 634 | // Media (audio/video) file. |
fab11235 | 635 | $code = $mediamanager->embed_url($url, $title, 0, 0, $embedoptions); |
9b56a34f PS |
636 | |
637 | } else { | |
638 | // anything else - just try object tag enlarged as much as possible | |
639 | $code = resourcelib_embed_general($url, $title, $clicktoopen, $mimetype); | |
640 | } | |
641 | ||
642 | return $code; | |
643 | } | |
1e7f8ea2 | 644 | |
e0e1a83e JMV |
645 | /** |
646 | * Logic to happen when a/some group(s) has/have been deleted in a course. | |
647 | * | |
648 | * @param int $courseid The course ID. | |
247980b0 | 649 | * @param int $groupid The group id if it is known |
e0e1a83e JMV |
650 | * @return void |
651 | */ | |
247980b0 | 652 | function lesson_process_group_deleted_in_course($courseid, $groupid = null) { |
e0e1a83e JMV |
653 | global $DB; |
654 | ||
e0e1a83e | 655 | $params = array('courseid' => $courseid); |
247980b0 JMV |
656 | if ($groupid) { |
657 | $params['groupid'] = $groupid; | |
658 | // We just update the group that was deleted. | |
659 | $sql = "SELECT o.id, o.lessonid | |
660 | FROM {lesson_overrides} o | |
661 | JOIN {lesson} lesson ON lesson.id = o.lessonid | |
662 | WHERE lesson.course = :courseid | |
663 | AND o.groupid = :groupid"; | |
664 | } else { | |
665 | // No groupid, we update all orphaned group overrides for all lessons in course. | |
666 | $sql = "SELECT o.id, o.lessonid | |
667 | FROM {lesson_overrides} o | |
668 | JOIN {lesson} lesson ON lesson.id = o.lessonid | |
669 | LEFT JOIN {groups} grp ON grp.id = o.groupid | |
670 | WHERE lesson.course = :courseid | |
671 | AND o.groupid IS NOT NULL | |
672 | AND grp.id IS NULL"; | |
673 | } | |
e0e1a83e JMV |
674 | $records = $DB->get_records_sql_menu($sql, $params); |
675 | if (!$records) { | |
676 | return; // Nothing to do. | |
677 | } | |
678 | $DB->delete_records_list('lesson_overrides', 'id', array_keys($records)); | |
679 | } | |
1e7f8ea2 | 680 | |
7d5564d9 JL |
681 | /** |
682 | * Return the overview report table and data. | |
683 | * | |
684 | * @param lesson $lesson lesson instance | |
685 | * @param mixed $currentgroup false if not group used, 0 for all groups, group id (int) to filter by that groups | |
686 | * @return mixed false if there is no information otherwise html_table and stdClass with the table and data | |
687 | * @since Moodle 3.3 | |
688 | */ | |
689 | function lesson_get_overview_report_table_and_data(lesson $lesson, $currentgroup) { | |
8c218004 JL |
690 | global $DB, $CFG; |
691 | require_once($CFG->dirroot . '/mod/lesson/pagetypes/branchtable.php'); | |
7d5564d9 JL |
692 | |
693 | $context = $lesson->context; | |
694 | $cm = $lesson->cm; | |
695 | // Count the number of branch and question pages in this lesson. | |
696 | $branchcount = $DB->count_records('lesson_pages', array('lessonid' => $lesson->id, 'qtype' => LESSON_PAGE_BRANCHTABLE)); | |
697 | $questioncount = ($DB->count_records('lesson_pages', array('lessonid' => $lesson->id)) - $branchcount); | |
698 | ||
699 | // Only load students if there attempts for this lesson. | |
700 | $attempts = $DB->record_exists('lesson_attempts', array('lessonid' => $lesson->id)); | |
701 | $branches = $DB->record_exists('lesson_branch', array('lessonid' => $lesson->id)); | |
702 | $timer = $DB->record_exists('lesson_timer', array('lessonid' => $lesson->id)); | |
703 | if ($attempts or $branches or $timer) { | |
704 | list($esql, $params) = get_enrolled_sql($context, '', $currentgroup, true); | |
705 | list($sort, $sortparams) = users_order_by_sql('u'); | |
706 | ||
6a8e25ef AH |
707 | $extrafields = get_extra_user_fields($context); |
708 | ||
7d5564d9 JL |
709 | $params['a1lessonid'] = $lesson->id; |
710 | $params['b1lessonid'] = $lesson->id; | |
711 | $params['c1lessonid'] = $lesson->id; | |
6a8e25ef | 712 | $ufields = user_picture::fields('u', $extrafields); |
7d5564d9 JL |
713 | $sql = "SELECT DISTINCT $ufields |
714 | FROM {user} u | |
715 | JOIN ( | |
716 | SELECT userid, lessonid FROM {lesson_attempts} a1 | |
717 | WHERE a1.lessonid = :a1lessonid | |
718 | UNION | |
719 | SELECT userid, lessonid FROM {lesson_branch} b1 | |
720 | WHERE b1.lessonid = :b1lessonid | |
721 | UNION | |
722 | SELECT userid, lessonid FROM {lesson_timer} c1 | |
723 | WHERE c1.lessonid = :c1lessonid | |
724 | ) a ON u.id = a.userid | |
725 | JOIN ($esql) ue ON ue.id = a.userid | |
726 | ORDER BY $sort"; | |
727 | ||
728 | $students = $DB->get_recordset_sql($sql, $params); | |
729 | if (!$students->valid()) { | |
730 | $students->close(); | |
731 | return array(false, false); | |
732 | } | |
733 | } else { | |
734 | return array(false, false); | |
735 | } | |
736 | ||
737 | if (! $grades = $DB->get_records('lesson_grades', array('lessonid' => $lesson->id), 'completed')) { | |
738 | $grades = array(); | |
739 | } | |
740 | ||
741 | if (! $times = $DB->get_records('lesson_timer', array('lessonid' => $lesson->id), 'starttime')) { | |
742 | $times = array(); | |
743 | } | |
744 | ||
745 | // Build an array for output. | |
746 | $studentdata = array(); | |
747 | ||
748 | $attempts = $DB->get_recordset('lesson_attempts', array('lessonid' => $lesson->id), 'timeseen'); | |
749 | foreach ($attempts as $attempt) { | |
750 | // if the user is not in the array or if the retry number is not in the sub array, add the data for that try. | |
751 | if (empty($studentdata[$attempt->userid]) || empty($studentdata[$attempt->userid][$attempt->retry])) { | |
752 | // restore/setup defaults | |
753 | $n = 0; | |
754 | $timestart = 0; | |
755 | $timeend = 0; | |
756 | $usergrade = null; | |
dd608921 | 757 | $eol = 0; |
7d5564d9 JL |
758 | |
759 | // search for the grade record for this try. if not there, the nulls defined above will be used. | |
760 | foreach($grades as $grade) { | |
761 | // check to see if the grade matches the correct user | |
762 | if ($grade->userid == $attempt->userid) { | |
763 | // see if n is = to the retry | |
764 | if ($n == $attempt->retry) { | |
765 | // get grade info | |
766 | $usergrade = round($grade->grade, 2); // round it here so we only have to do it once | |
767 | break; | |
768 | } | |
769 | $n++; // if not equal, then increment n | |
770 | } | |
771 | } | |
772 | $n = 0; | |
773 | // search for the time record for this try. if not there, the nulls defined above will be used. | |
774 | foreach($times as $time) { | |
775 | // check to see if the grade matches the correct user | |
776 | if ($time->userid == $attempt->userid) { | |
777 | // see if n is = to the retry | |
778 | if ($n == $attempt->retry) { | |
779 | // get grade info | |
780 | $timeend = $time->lessontime; | |
781 | $timestart = $time->starttime; | |
782 | $eol = $time->completed; | |
783 | break; | |
784 | } | |
785 | $n++; // if not equal, then increment n | |
786 | } | |
787 | } | |
788 | ||
789 | // build up the array. | |
790 | // this array represents each student and all of their tries at the lesson | |
791 | $studentdata[$attempt->userid][$attempt->retry] = array( "timestart" => $timestart, | |
792 | "timeend" => $timeend, | |
793 | "grade" => $usergrade, | |
794 | "end" => $eol, | |
795 | "try" => $attempt->retry, | |
796 | "userid" => $attempt->userid); | |
797 | } | |
798 | } | |
799 | $attempts->close(); | |
800 | ||
801 | $branches = $DB->get_recordset('lesson_branch', array('lessonid' => $lesson->id), 'timeseen'); | |
802 | foreach ($branches as $branch) { | |
803 | // If the user is not in the array or if the retry number is not in the sub array, add the data for that try. | |
804 | if (empty($studentdata[$branch->userid]) || empty($studentdata[$branch->userid][$branch->retry])) { | |
805 | // Restore/setup defaults. | |
806 | $n = 0; | |
807 | $timestart = 0; | |
808 | $timeend = 0; | |
809 | $usergrade = null; | |
dd608921 | 810 | $eol = 0; |
7d5564d9 JL |
811 | // Search for the time record for this try. if not there, the nulls defined above will be used. |
812 | foreach ($times as $time) { | |
813 | // Check to see if the grade matches the correct user. | |
814 | if ($time->userid == $branch->userid) { | |
815 | // See if n is = to the retry. | |
816 | if ($n == $branch->retry) { | |
817 | // Get grade info. | |
818 | $timeend = $time->lessontime; | |
819 | $timestart = $time->starttime; | |
820 | $eol = $time->completed; | |
821 | break; | |
822 | } | |
823 | $n++; // If not equal, then increment n. | |
824 | } | |
825 | } | |
826 | ||
827 | // Build up the array. | |
828 | // This array represents each student and all of their tries at the lesson. | |
829 | $studentdata[$branch->userid][$branch->retry] = array( "timestart" => $timestart, | |
830 | "timeend" => $timeend, | |
831 | "grade" => $usergrade, | |
832 | "end" => $eol, | |
833 | "try" => $branch->retry, | |
834 | "userid" => $branch->userid); | |
835 | } | |
836 | } | |
837 | $branches->close(); | |
838 | ||
839 | // Need the same thing for timed entries that were not completed. | |
840 | foreach ($times as $time) { | |
841 | $endoflesson = $time->completed; | |
842 | // If the time start is the same with another record then we shouldn't be adding another item to this array. | |
843 | if (isset($studentdata[$time->userid])) { | |
844 | $foundmatch = false; | |
845 | $n = 0; | |
846 | foreach ($studentdata[$time->userid] as $key => $value) { | |
847 | if ($value['timestart'] == $time->starttime) { | |
848 | // Don't add this to the array. | |
849 | $foundmatch = true; | |
850 | break; | |
851 | } | |
852 | } | |
853 | $n = count($studentdata[$time->userid]) + 1; | |
854 | if (!$foundmatch) { | |
855 | // Add a record. | |
856 | $studentdata[$time->userid][] = array( | |
857 | "timestart" => $time->starttime, | |
858 | "timeend" => $time->lessontime, | |
859 | "grade" => null, | |
860 | "end" => $endoflesson, | |
861 | "try" => $n, | |
862 | "userid" => $time->userid | |
863 | ); | |
864 | } | |
865 | } else { | |
866 | $studentdata[$time->userid][] = array( | |
867 | "timestart" => $time->starttime, | |
868 | "timeend" => $time->lessontime, | |
869 | "grade" => null, | |
870 | "end" => $endoflesson, | |
871 | "try" => 0, | |
872 | "userid" => $time->userid | |
873 | ); | |
874 | } | |
875 | } | |
876 | ||
877 | // To store all the data to be returned by the function. | |
878 | $data = new stdClass(); | |
879 | ||
880 | // Determine if lesson should have a score. | |
881 | if ($branchcount > 0 AND $questioncount == 0) { | |
882 | // This lesson only contains content pages and is not graded. | |
883 | $data->lessonscored = false; | |
884 | } else { | |
885 | // This lesson is graded. | |
886 | $data->lessonscored = true; | |
887 | } | |
888 | // set all the stats variables | |
889 | $data->numofattempts = 0; | |
890 | $data->avescore = 0; | |
891 | $data->avetime = 0; | |
892 | $data->highscore = null; | |
893 | $data->lowscore = null; | |
894 | $data->hightime = null; | |
895 | $data->lowtime = null; | |
896 | $data->students = array(); | |
897 | ||
898 | $table = new html_table(); | |
899 | ||
6a8e25ef AH |
900 | $headers = [get_string('name')]; |
901 | ||
902 | foreach ($extrafields as $field) { | |
903 | $headers[] = get_user_field_name($field); | |
904 | } | |
905 | ||
906 | $headers [] = get_string('attempts', 'lesson'); | |
907 | ||
7d5564d9 JL |
908 | // Set up the table object. |
909 | if ($data->lessonscored) { | |
6a8e25ef AH |
910 | $headers [] = get_string('highscore', 'lesson'); |
911 | } | |
912 | ||
913 | $colcount = count($headers); | |
914 | ||
915 | $table->head = $headers; | |
916 | ||
917 | $table->align = []; | |
918 | $table->align = array_pad($table->align, $colcount, 'center'); | |
919 | $table->align[$colcount - 1] = 'left'; | |
920 | ||
921 | if ($data->lessonscored) { | |
922 | $table->align[$colcount - 2] = 'left'; | |
7d5564d9 | 923 | } |
6a8e25ef AH |
924 | |
925 | $table->wrap = []; | |
926 | $table->wrap = array_pad($table->wrap, $colcount, 'nowrap'); | |
927 | ||
7d5564d9 | 928 | $table->attributes['class'] = 'standardtable generaltable'; |
7d5564d9 JL |
929 | |
930 | // print out the $studentdata array | |
931 | // going through each student that has attempted the lesson, so, each student should have something to be displayed | |
932 | foreach ($students as $student) { | |
933 | // check to see if the student has attempts to print out | |
934 | if (array_key_exists($student->id, $studentdata)) { | |
935 | // set/reset some variables | |
936 | $attempts = array(); | |
937 | $dataforstudent = new stdClass; | |
938 | $dataforstudent->attempts = array(); | |
939 | // gather the data for each user attempt | |
940 | $bestgrade = 0; | |
941 | $bestgradefound = false; | |
942 | // $tries holds all the tries/retries a student has done | |
943 | $tries = $studentdata[$student->id]; | |
944 | $studentname = fullname($student, true); | |
945 | ||
946 | foreach ($tries as $try) { | |
947 | $dataforstudent->attempts[] = $try; | |
948 | ||
949 | // Start to build up the checkbox and link. | |
950 | if (has_capability('mod/lesson:edit', $context)) { | |
951 | $temp = '<input type="checkbox" id="attempts" name="attempts['.$try['userid'].']['.$try['try'].']" /> '; | |
952 | } else { | |
953 | $temp = ''; | |
954 | } | |
955 | ||
956 | $temp .= "<a href=\"report.php?id=$cm->id&action=reportdetail&userid=".$try['userid'] | |
957 | .'&try='.$try['try'].'" class="lesson-attempt-link">'; | |
958 | if ($try["grade"] !== null) { // if null then not done yet | |
959 | // this is what the link does when the user has completed the try | |
960 | $timetotake = $try["timeend"] - $try["timestart"]; | |
961 | ||
962 | $temp .= $try["grade"]."%"; | |
963 | $bestgradefound = true; | |
964 | if ($try["grade"] > $bestgrade) { | |
965 | $bestgrade = $try["grade"]; | |
966 | } | |
967 | $temp .= " ".userdate($try["timestart"]); | |
968 | $temp .= ", (".format_time($timetotake).")</a>"; | |
969 | } else { | |
970 | if ($try["end"]) { | |
971 | // User finished the lesson but has no grade. (Happens when there are only content pages). | |
972 | $temp .= " ".userdate($try["timestart"]); | |
973 | $timetotake = $try["timeend"] - $try["timestart"]; | |
974 | $temp .= ", (".format_time($timetotake).")</a>"; | |
975 | } else { | |
976 | // This is what the link does/looks like when the user has not completed the attempt. | |
977 | $temp .= get_string("notcompleted", "lesson"); | |
978 | if ($try['timestart'] !== 0) { | |
979 | // Teacher previews do not track time spent. | |
980 | $temp .= " ".userdate($try["timestart"]); | |
981 | } | |
982 | $temp .= "</a>"; | |
983 | $timetotake = null; | |
984 | } | |
985 | } | |
986 | // build up the attempts array | |
987 | $attempts[] = $temp; | |
988 | ||
989 | // Run these lines for the stats only if the user finnished the lesson. | |
990 | if ($try["end"]) { | |
991 | // User has completed the lesson. | |
992 | $data->numofattempts++; | |
993 | $data->avetime += $timetotake; | |
994 | if ($timetotake > $data->hightime || $data->hightime == null) { | |
995 | $data->hightime = $timetotake; | |
996 | } | |
997 | if ($timetotake < $data->lowtime || $data->lowtime == null) { | |
998 | $data->lowtime = $timetotake; | |
999 | } | |
1000 | if ($try["grade"] !== null) { | |
1001 | // The lesson was scored. | |
1002 | $data->avescore += $try["grade"]; | |
1003 | if ($try["grade"] > $data->highscore || $data->highscore === null) { | |
1004 | $data->highscore = $try["grade"]; | |
1005 | } | |
1006 | if ($try["grade"] < $data->lowscore || $data->lowscore === null) { | |
1007 | $data->lowscore = $try["grade"]; | |
1008 | } | |
1009 | ||
1010 | } | |
1011 | } | |
1012 | } | |
1013 | // get line breaks in after each attempt | |
1014 | $attempts = implode("<br />\n", $attempts); | |
6a8e25ef AH |
1015 | $row = [$studentname]; |
1016 | ||
1017 | foreach ($extrafields as $field) { | |
1018 | $row[] = $student->$field; | |
1019 | } | |
1020 | ||
1021 | $row[] = $attempts; | |
7d5564d9 JL |
1022 | |
1023 | if ($data->lessonscored) { | |
1024 | // Add the grade if the lesson is graded. | |
6a8e25ef | 1025 | $row[] = $bestgrade."%"; |
7d5564d9 | 1026 | } |
6a8e25ef AH |
1027 | |
1028 | $table->data[] = $row; | |
1029 | ||
7d5564d9 JL |
1030 | // Add the student data. |
1031 | $dataforstudent->id = $student->id; | |
1032 | $dataforstudent->fullname = $studentname; | |
1033 | $dataforstudent->bestgrade = $bestgrade; | |
1034 | $data->students[] = $dataforstudent; | |
1035 | } | |
1036 | } | |
1037 | $students->close(); | |
1038 | if ($data->numofattempts > 0) { | |
1039 | $data->avescore = $data->avescore / $data->numofattempts; | |
1040 | } | |
1041 | ||
1042 | return array($table, $data); | |
1043 | } | |
1044 | ||
49e35378 JL |
1045 | /** |
1046 | * Return information about one user attempt (including answers) | |
1047 | * @param lesson $lesson lesson instance | |
1048 | * @param int $userid the user id | |
1049 | * @param int $attempt the attempt number | |
1050 | * @return array the user answers (array) and user data stats (object) | |
1051 | * @since Moodle 3.3 | |
1052 | */ | |
1053 | function lesson_get_user_detailed_report_data(lesson $lesson, $userid, $attempt) { | |
1054 | global $DB; | |
1055 | ||
1056 | $context = $lesson->context; | |
1057 | if (!empty($userid)) { | |
1058 | // Apply overrides. | |
1059 | $lesson->update_effective_access($userid); | |
1060 | } | |
1061 | ||
1062 | $lessonpages = $lesson->load_all_pages(); | |
1063 | foreach ($lessonpages as $lessonpage) { | |
1064 | if ($lessonpage->prevpageid == 0) { | |
1065 | $pageid = $lessonpage->id; | |
1066 | } | |
1067 | } | |
1068 | ||
1069 | // now gather the stats into an object | |
1070 | $firstpageid = $pageid; | |
1071 | $pagestats = array(); | |
1072 | while ($pageid != 0) { // EOL | |
1073 | $page = $lessonpages[$pageid]; | |
1074 | $params = array ("lessonid" => $lesson->id, "pageid" => $page->id); | |
1075 | if ($allanswers = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND pageid = :pageid", $params, "timeseen")) { | |
1076 | // get them ready for processing | |
1077 | $orderedanswers = array(); | |
1078 | foreach ($allanswers as $singleanswer) { | |
1079 | // ordering them like this, will help to find the single attempt record that we want to keep. | |
1080 | $orderedanswers[$singleanswer->userid][$singleanswer->retry][] = $singleanswer; | |
1081 | } | |
1082 | // this is foreach user and for each try for that user, keep one attempt record | |
1083 | foreach ($orderedanswers as $orderedanswer) { | |
1084 | foreach($orderedanswer as $tries) { | |
1085 | $page->stats($pagestats, $tries); | |
1086 | } | |
1087 | } | |
1088 | } else { | |
1089 | // no one answered yet... | |
1090 | } | |
1091 | //unset($orderedanswers); initialized above now | |
1092 | $pageid = $page->nextpageid; | |
1093 | } | |
1094 | ||
1095 | $manager = lesson_page_type_manager::get($lesson); | |
1096 | $qtypes = $manager->get_page_type_strings(); | |
1097 | ||
1098 | $answerpages = array(); | |
1099 | $answerpage = ""; | |
1100 | $pageid = $firstpageid; | |
1101 | // cycle through all the pages | |
1102 | // foreach page, add to the $answerpages[] array all the data that is needed | |
1103 | // from the question, the users attempt, and the statistics | |
1104 | // grayout pages that the user did not answer and Branch, end of branch, cluster | |
1105 | // and end of cluster pages | |
1106 | while ($pageid != 0) { // EOL | |
1107 | $page = $lessonpages[$pageid]; | |
1108 | $answerpage = new stdClass; | |
63130e32 JL |
1109 | // Keep the original page object. |
1110 | $answerpage->page = $page; | |
49e35378 JL |
1111 | $data =''; |
1112 | ||
1113 | $answerdata = new stdClass; | |
1114 | // Set some defaults for the answer data. | |
1115 | $answerdata->score = null; | |
1116 | $answerdata->response = null; | |
1117 | $answerdata->responseformat = FORMAT_PLAIN; | |
1118 | ||
1119 | $answerpage->title = format_string($page->title); | |
1120 | ||
1121 | $options = new stdClass; | |
1122 | $options->noclean = true; | |
1123 | $options->overflowdiv = true; | |
1124 | $options->context = $context; | |
1125 | $answerpage->contents = format_text($page->contents, $page->contentsformat, $options); | |
1126 | ||
1127 | $answerpage->qtype = $qtypes[$page->qtype].$page->option_description_string(); | |
1128 | $answerpage->grayout = $page->grayout; | |
1129 | $answerpage->context = $context; | |
1130 | ||
1131 | if (empty($userid)) { | |
1132 | // there is no userid, so set these vars and display stats. | |
1133 | $answerpage->grayout = 0; | |
1134 | $useranswer = null; | |
1135 | } elseif ($useranswers = $DB->get_records("lesson_attempts",array("lessonid"=>$lesson->id, "userid"=>$userid, "retry"=>$attempt,"pageid"=>$page->id), "timeseen")) { | |
1136 | // get the user's answer for this page | |
1137 | // need to find the right one | |
1138 | $i = 0; | |
1139 | foreach ($useranswers as $userattempt) { | |
1140 | $useranswer = $userattempt; | |
1141 | $i++; | |
1142 | if ($lesson->maxattempts == $i) { | |
1143 | break; // reached maxattempts, break out | |
1144 | } | |
1145 | } | |
1146 | } else { | |
1147 | // user did not answer this page, gray it out and set some nulls | |
1148 | $answerpage->grayout = 1; | |
1149 | $useranswer = null; | |
1150 | } | |
1151 | $i = 0; | |
1152 | $n = 0; | |
1153 | $answerpages[] = $page->report_answers(clone($answerpage), clone($answerdata), $useranswer, $pagestats, $i, $n); | |
1154 | $pageid = $page->nextpageid; | |
1155 | } | |
1156 | ||
1157 | $userstats = new stdClass; | |
1158 | if (!empty($userid)) { | |
1159 | $params = array("lessonid"=>$lesson->id, "userid"=>$userid); | |
1160 | ||
1161 | $alreadycompleted = true; | |
1162 | ||
1163 | if (!$grades = $DB->get_records_select("lesson_grades", "lessonid = :lessonid and userid = :userid", $params, "completed", "*", $attempt, 1)) { | |
1164 | $userstats->grade = -1; | |
1165 | $userstats->completed = -1; | |
1166 | $alreadycompleted = false; | |
1167 | } else { | |
1168 | $userstats->grade = current($grades); | |
1169 | $userstats->completed = $userstats->grade->completed; | |
1170 | $userstats->grade = round($userstats->grade->grade, 2); | |
1171 | } | |
1172 | ||
1173 | if (!$times = $lesson->get_user_timers($userid, 'starttime', '*', $attempt, 1)) { | |
1174 | $userstats->timetotake = -1; | |
1175 | $alreadycompleted = false; | |
1176 | } else { | |
1177 | $userstats->timetotake = current($times); | |
1178 | $userstats->timetotake = $userstats->timetotake->lessontime - $userstats->timetotake->starttime; | |
1179 | } | |
1180 | ||
1181 | if ($alreadycompleted) { | |
1182 | $userstats->gradeinfo = lesson_grade($lesson, $attempt, $userid); | |
1183 | } | |
1184 | } | |
1185 | ||
1186 | return array($answerpages, $userstats); | |
1187 | } | |
1188 | ||
1189 | ||
1e7f8ea2 PS |
1190 | /** |
1191 | * Abstract class that page type's MUST inherit from. | |
1192 | * | |
1193 | * This is the abstract class that ALL add page type forms must extend. | |
1194 | * You will notice that all but two of the methods this class contains are final. | |
1195 | * Essentially the only thing that extending classes can do is extend custom_definition. | |
1196 | * OR if it has a special requirement on creation it can extend construction_override | |
1197 | * | |
1198 | * @abstract | |
cc3dbaaa PS |
1199 | * @copyright 2009 Sam Hemelryk |
1200 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1e7f8ea2 PS |
1201 | */ |
1202 | abstract class lesson_add_page_form_base extends moodleform { | |
1203 | ||
1204 | /** | |
1205 | * This is the classic define that is used to identify this pagetype. | |
1206 | * Will be one of LESSON_* | |
1207 | * @var int | |
1208 | */ | |
1209 | public $qtype; | |
1210 | ||
1211 | /** | |
1212 | * The simple string that describes the page type e.g. truefalse, multichoice | |
1213 | * @var string | |
1214 | */ | |
1215 | public $qtypestring; | |
1216 | ||
1217 | /** | |
1218 | * An array of options used in the htmleditor | |
1219 | * @var array | |
1220 | */ | |
1221 | protected $editoroptions = array(); | |
1222 | ||
1223 | /** | |
1224 | * True if this is a standard page of false if it does something special. | |
1225 | * Questions are standard pages, branch tables are not | |
1226 | * @var bool | |
1227 | */ | |
1228 | protected $standard = true; | |
1229 | ||
ceeab150 RT |
1230 | /** |
1231 | * Answer format supported by question type. | |
1232 | */ | |
1233 | protected $answerformat = ''; | |
1234 | ||
1235 | /** | |
1236 | * Response format supported by question type. | |
1237 | */ | |
1238 | protected $responseformat = ''; | |
1239 | ||
1e7f8ea2 PS |
1240 | /** |
1241 | * Each page type can and should override this to add any custom elements to | |
1242 | * the basic form that they want | |
1243 | */ | |
1244 | public function custom_definition() {} | |
1245 | ||
1246 | /** | |
ceeab150 RT |
1247 | * Returns answer format used by question type. |
1248 | */ | |
1249 | public function get_answer_format() { | |
1250 | return $this->answerformat; | |
1251 | } | |
1252 | ||
1253 | /** | |
1254 | * Returns response format used by question type. | |
1255 | */ | |
1256 | public function get_response_format() { | |
1257 | return $this->responseformat; | |
1258 | } | |
1259 | ||
1260 | /** | |
1e7f8ea2 PS |
1261 | * Used to determine if this is a standard page or a special page |
1262 | * @return bool | |
1263 | */ | |
1264 | public final function is_standard() { | |
1265 | return (bool)$this->standard; | |
1266 | } | |
1267 | ||
1268 | /** | |
1269 | * Add the required basic elements to the form. | |
1270 | * | |
1271 | * This method adds the basic elements to the form including title and contents | |
1272 | * and then calls custom_definition(); | |
1273 | */ | |
1274 | public final function definition() { | |
1275 | $mform = $this->_form; | |
1276 | $editoroptions = $this->_customdata['editoroptions']; | |
1277 | ||
a06face2 JE |
1278 | if ($this->qtypestring != 'selectaqtype') { |
1279 | if ($this->_customdata['edit']) { | |
1280 | $mform->addElement('header', 'qtypeheading', get_string('edit'. $this->qtypestring, 'lesson')); | |
1281 | } else { | |
1282 | $mform->addElement('header', 'qtypeheading', get_string('add'. $this->qtypestring, 'lesson')); | |
1283 | } | |
1284 | } | |
1e7f8ea2 | 1285 | |
32495414 MA |
1286 | if (!empty($this->_customdata['returnto'])) { |
1287 | $mform->addElement('hidden', 'returnto', $this->_customdata['returnto']); | |
1288 | $mform->setType('returnto', PARAM_URL); | |
1289 | } | |
1290 | ||
1e7f8ea2 PS |
1291 | $mform->addElement('hidden', 'id'); |
1292 | $mform->setType('id', PARAM_INT); | |
1293 | ||
1294 | $mform->addElement('hidden', 'pageid'); | |
1295 | $mform->setType('pageid', PARAM_INT); | |
1296 | ||
1297 | if ($this->standard === true) { | |
1298 | $mform->addElement('hidden', 'qtype'); | |
05db3cc7 | 1299 | $mform->setType('qtype', PARAM_INT); |
1e7f8ea2 | 1300 | |
3fa2f030 | 1301 | $mform->addElement('text', 'title', get_string('pagetitle', 'lesson'), array('size'=>70)); |
1e7f8ea2 | 1302 | $mform->setType('title', PARAM_TEXT); |
3fa2f030 PS |
1303 | $mform->addRule('title', get_string('required'), 'required', null, 'client'); |
1304 | ||
1e7f8ea2 | 1305 | $this->editoroptions = array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$this->_customdata['maxbytes']); |
3fa2f030 PS |
1306 | $mform->addElement('editor', 'contents_editor', get_string('pagecontents', 'lesson'), null, $this->editoroptions); |
1307 | $mform->setType('contents_editor', PARAM_RAW); | |
1308 | $mform->addRule('contents_editor', get_string('required'), 'required', null, 'client'); | |
1e7f8ea2 PS |
1309 | } |
1310 | ||
1311 | $this->custom_definition(); | |
1312 | ||
1313 | if ($this->_customdata['edit'] === true) { | |
1314 | $mform->addElement('hidden', 'edit', 1); | |
747d5b1c | 1315 | $mform->setType('edit', PARAM_BOOL); |
3fa2f030 | 1316 | $this->add_action_buttons(get_string('cancel'), get_string('savepage', 'lesson')); |
272bc0a6 | 1317 | } else if ($this->qtype === 'questiontype') { |
3fa2f030 | 1318 | $this->add_action_buttons(get_string('cancel'), get_string('addaquestionpage', 'lesson')); |
272bc0a6 RW |
1319 | } else { |
1320 | $this->add_action_buttons(get_string('cancel'), get_string('savepage', 'lesson')); | |
1e7f8ea2 PS |
1321 | } |
1322 | } | |
1323 | ||
1324 | /** | |
1325 | * Convenience function: Adds a jumpto select element | |
1326 | * | |
1327 | * @param string $name | |
1328 | * @param string|null $label | |
1329 | * @param int $selected The page to select by default | |
1330 | */ | |
1331 | protected final function add_jumpto($name, $label=null, $selected=LESSON_NEXTPAGE) { | |
1332 | $title = get_string("jump", "lesson"); | |
1333 | if ($label === null) { | |
1334 | $label = $title; | |
1335 | } | |
1336 | if (is_int($name)) { | |
1337 | $name = "jumpto[$name]"; | |
1338 | } | |
1339 | $this->_form->addElement('select', $name, $label, $this->_customdata['jumpto']); | |
1340 | $this->_form->setDefault($name, $selected); | |
1341 | $this->_form->addHelpButton($name, 'jumps', 'lesson'); | |
1342 | } | |
1343 | ||
1344 | /** | |
1345 | * Convenience function: Adds a score input element | |
1346 | * | |
1347 | * @param string $name | |
1348 | * @param string|null $label | |
1349 | * @param mixed $value The default value | |
1350 | */ | |
1351 | protected final function add_score($name, $label=null, $value=null) { | |
1352 | if ($label === null) { | |
1353 | $label = get_string("score", "lesson"); | |
1354 | } | |
a015bc03 | 1355 | |
1e7f8ea2 PS |
1356 | if (is_int($name)) { |
1357 | $name = "score[$name]"; | |
1358 | } | |
1359 | $this->_form->addElement('text', $name, $label, array('size'=>5)); | |
a015bc03 | 1360 | $this->_form->setType($name, PARAM_INT); |
1e7f8ea2 PS |
1361 | if ($value !== null) { |
1362 | $this->_form->setDefault($name, $value); | |
1363 | } | |
20685b28 AG |
1364 | $this->_form->addHelpButton($name, 'score', 'lesson'); |
1365 | ||
1366 | // Score is only used for custom scoring. Disable the element when not in use to stop some confusion. | |
1367 | if (!$this->_customdata['lesson']->custom) { | |
1368 | $this->_form->freeze($name); | |
1369 | } | |
1e7f8ea2 PS |
1370 | } |
1371 | ||
1372 | /** | |
1373 | * Convenience function: Adds an answer editor | |
1374 | * | |
1375 | * @param int $count The count of the element to add | |
ecea65ca | 1376 | * @param string $label, null means default |
a675ada5 | 1377 | * @param bool $required |
a1556aaf | 1378 | * @param string $format |
a675ada5 | 1379 | * @return void |
1e7f8ea2 | 1380 | */ |
a1556aaf | 1381 | protected final function add_answer($count, $label = null, $required = false, $format= '') { |
ecea65ca | 1382 | if ($label === null) { |
a675ada5 PS |
1383 | $label = get_string('answer', 'lesson'); |
1384 | } | |
0abc18cf | 1385 | |
a1556aaf | 1386 | if ($format == LESSON_ANSWER_HTML) { |
0abc18cf JMV |
1387 | $this->_form->addElement('editor', 'answer_editor['.$count.']', $label, |
1388 | array('rows' => '4', 'columns' => '80'), | |
1389 | array('noclean' => true, 'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes' => $this->_customdata['maxbytes'])); | |
1390 | $this->_form->setType('answer_editor['.$count.']', PARAM_RAW); | |
1391 | $this->_form->setDefault('answer_editor['.$count.']', array('text' => '', 'format' => FORMAT_HTML)); | |
a1556aaf | 1392 | } else { |
a1300e98 JMV |
1393 | $this->_form->addElement('text', 'answer_editor['.$count.']', $label, |
1394 | array('size' => '50', 'maxlength' => '200')); | |
1395 | $this->_form->setType('answer_editor['.$count.']', PARAM_TEXT); | |
0abc18cf JMV |
1396 | } |
1397 | ||
a675ada5 PS |
1398 | if ($required) { |
1399 | $this->_form->addRule('answer_editor['.$count.']', get_string('required'), 'required', null, 'client'); | |
1400 | } | |
1e7f8ea2 PS |
1401 | } |
1402 | /** | |
1403 | * Convenience function: Adds an response editor | |
1404 | * | |
1405 | * @param int $count The count of the element to add | |
ecea65ca | 1406 | * @param string $label, null means default |
a675ada5 PS |
1407 | * @param bool $required |
1408 | * @return void | |
1e7f8ea2 | 1409 | */ |
ecea65ca RW |
1410 | protected final function add_response($count, $label = null, $required = false) { |
1411 | if ($label === null) { | |
a675ada5 PS |
1412 | $label = get_string('response', 'lesson'); |
1413 | } | |
0abc18cf JMV |
1414 | $this->_form->addElement('editor', 'response_editor['.$count.']', $label, |
1415 | array('rows' => '4', 'columns' => '80'), | |
1416 | array('noclean' => true, 'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes' => $this->_customdata['maxbytes'])); | |
1417 | $this->_form->setType('response_editor['.$count.']', PARAM_RAW); | |
1418 | $this->_form->setDefault('response_editor['.$count.']', array('text' => '', 'format' => FORMAT_HTML)); | |
1419 | ||
a675ada5 PS |
1420 | if ($required) { |
1421 | $this->_form->addRule('response_editor['.$count.']', get_string('required'), 'required', null, 'client'); | |
1422 | } | |
1e7f8ea2 PS |
1423 | } |
1424 | ||
1425 | /** | |
1426 | * A function that gets called upon init of this object by the calling script. | |
1427 | * | |
1428 | * This can be used to process an immediate action if required. Currently it | |
1429 | * is only used in special cases by non-standard page types. | |
1430 | * | |
1431 | * @return bool | |
1432 | */ | |
0ed6f712 | 1433 | public function construction_override($pageid, lesson $lesson) { |
1e7f8ea2 PS |
1434 | return true; |
1435 | } | |
1436 | } | |
1437 | ||
1438 | ||
1439 | ||
1440 | /** | |
1441 | * Class representation of a lesson | |
1442 | * | |
1443 | * This class is used the interact with, and manage a lesson once instantiated. | |
1444 | * If you need to fetch a lesson object you can do so by calling | |
1445 | * | |
1446 | * <code> | |
1447 | * lesson::load($lessonid); | |
1448 | * // or | |
1449 | * $lessonrecord = $DB->get_record('lesson', $lessonid); | |
1450 | * $lesson = new lesson($lessonrecord); | |
1451 | * </code> | |
1452 | * | |
1453 | * The class itself extends lesson_base as all classes within the lesson module should | |
1454 | * | |
1455 | * These properties are from the database | |
1456 | * @property int $id The id of this lesson | |
1457 | * @property int $course The ID of the course this lesson belongs to | |
1458 | * @property string $name The name of this lesson | |
1459 | * @property int $practice Flag to toggle this as a practice lesson | |
1460 | * @property int $modattempts Toggle to allow the user to go back and review answers | |
1461 | * @property int $usepassword Toggle the use of a password for entry | |
1462 | * @property string $password The password to require users to enter | |
ff85f902 | 1463 | * @property int $dependency ID of another lesson this lesson is dependent on |
1e7f8ea2 PS |
1464 | * @property string $conditions Conditions of the lesson dependency |
1465 | * @property int $grade The maximum grade a user can achieve (%) | |
1466 | * @property int $custom Toggle custom scoring on or off | |
1467 | * @property int $ongoing Toggle display of an ongoing score | |
1468 | * @property int $usemaxgrade How retakes are handled (max=1, mean=0) | |
1469 | * @property int $maxanswers The max number of answers or branches | |
1470 | * @property int $maxattempts The maximum number of attempts a user can record | |
1471 | * @property int $review Toggle use or wrong answer review button | |
1472 | * @property int $nextpagedefault Override the default next page | |
1473 | * @property int $feedback Toggles display of default feedback | |
1474 | * @property int $minquestions Sets a minimum value of pages seen when calculating grades | |
1475 | * @property int $maxpages Maximum number of pages this lesson can contain | |
1476 | * @property int $retake Flag to allow users to retake a lesson | |
1477 | * @property int $activitylink Relate this lesson to another lesson | |
1478 | * @property string $mediafile File to pop up to or webpage to display | |
1479 | * @property int $mediaheight Sets the height of the media file popup | |
1480 | * @property int $mediawidth Sets the width of the media file popup | |
1481 | * @property int $mediaclose Toggle display of a media close button | |
1482 | * @property int $slideshow Flag for whether branch pages should be shown as slideshows | |
1483 | * @property int $width Width of slideshow | |
1484 | * @property int $height Height of slideshow | |
1485 | * @property string $bgcolor Background colour of slideshow | |
ff85f902 | 1486 | * @property int $displayleft Display a left menu |
1e7f8ea2 PS |
1487 | * @property int $displayleftif Sets the condition on which the left menu is displayed |
1488 | * @property int $progressbar Flag to toggle display of a lesson progress bar | |
1e7f8ea2 PS |
1489 | * @property int $available Timestamp of when this lesson becomes available |
1490 | * @property int $deadline Timestamp of when this lesson is no longer available | |
1491 | * @property int $timemodified Timestamp when lesson was last modified | |
87e472bd | 1492 | * @property int $allowofflineattempts Whether to allow the lesson to be attempted offline in the mobile app |
1e7f8ea2 PS |
1493 | * |
1494 | * These properties are calculated | |
1495 | * @property int $firstpageid Id of the first page of this lesson (prevpageid=0) | |
1496 | * @property int $lastpageid Id of the last page of this lesson (nextpageid=0) | |
1497 | * | |
cc3dbaaa PS |
1498 | * @copyright 2009 Sam Hemelryk |
1499 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1e7f8ea2 PS |
1500 | */ |
1501 | class lesson extends lesson_base { | |
1502 | ||
1503 | /** | |
1504 | * The id of the first page (where prevpageid = 0) gets set and retrieved by | |
1505 | * {@see get_firstpageid()} by directly calling <code>$lesson->firstpageid;</code> | |
1506 | * @var int | |
1507 | */ | |
1508 | protected $firstpageid = null; | |
1509 | /** | |
1510 | * The id of the last page (where nextpageid = 0) gets set and retrieved by | |
1511 | * {@see get_lastpageid()} by directly calling <code>$lesson->lastpageid;</code> | |
1512 | * @var int | |
1513 | */ | |
1514 | protected $lastpageid = null; | |
1515 | /** | |
1516 | * An array used to cache the pages associated with this lesson after the first | |
1517 | * time they have been loaded. | |
1518 | * A note to developers: If you are going to be working with MORE than one or | |
1519 | * two pages from a lesson you should probably call {@see $lesson->load_all_pages()} | |
1520 | * in order to save excess database queries. | |
1521 | * @var array An array of lesson_page objects | |
1522 | */ | |
1523 | protected $pages = array(); | |
1524 | /** | |
1525 | * Flag that gets set to true once all of the pages associated with the lesson | |
1526 | * have been loaded. | |
1527 | * @var bool | |
1528 | */ | |
1529 | protected $loadedallpages = false; | |
1530 | ||
4a3391b6 JL |
1531 | /** |
1532 | * Course module object gets set and retrieved by directly calling <code>$lesson->cm;</code> | |
1533 | * @see get_cm() | |
1534 | * @var stdClass | |
1535 | */ | |
1536 | protected $cm = null; | |
1537 | ||
7d7a2a4e JL |
1538 | /** |
1539 | * Course object gets set and retrieved by directly calling <code>$lesson->courserecord;</code> | |
1540 | * @see get_courserecord() | |
1541 | * @var stdClass | |
1542 | */ | |
1543 | protected $courserecord = null; | |
1544 | ||
4a3391b6 JL |
1545 | /** |
1546 | * Context object gets set and retrieved by directly calling <code>$lesson->context;</code> | |
1547 | * @see get_context() | |
1548 | * @var stdClass | |
1549 | */ | |
1550 | protected $context = null; | |
1551 | ||
1552 | /** | |
1553 | * Constructor method | |
1554 | * | |
1555 | * @param object $properties | |
1556 | * @param stdClass $cm course module object | |
7d7a2a4e | 1557 | * @param stdClass $course course object |
4a3391b6 JL |
1558 | * @since Moodle 3.3 |
1559 | */ | |
7d7a2a4e | 1560 | public function __construct($properties, $cm = null, $course = null) { |
4a3391b6 JL |
1561 | parent::__construct($properties); |
1562 | $this->cm = $cm; | |
7d7a2a4e | 1563 | $this->courserecord = $course; |
4a3391b6 JL |
1564 | } |
1565 | ||
1e7f8ea2 PS |
1566 | /** |
1567 | * Simply generates a lesson object given an array/object of properties | |
1568 | * Overrides {@see lesson_base->create()} | |
1569 | * @static | |
1570 | * @param object|array $properties | |
1571 | * @return lesson | |
1572 | */ | |
1573 | public static function create($properties) { | |
1574 | return new lesson($properties); | |
1575 | } | |
1576 | ||
1577 | /** | |
1578 | * Generates a lesson object from the database given its id | |
1579 | * @static | |
1580 | * @param int $lessonid | |
1581 | * @return lesson | |
1582 | */ | |
1583 | public static function load($lessonid) { | |
3983f2dc PS |
1584 | global $DB; |
1585 | ||
1e7f8ea2 PS |
1586 | if (!$lesson = $DB->get_record('lesson', array('id' => $lessonid))) { |
1587 | print_error('invalidcoursemodule'); | |
1588 | } | |
1589 | return new lesson($lesson); | |
1590 | } | |
1591 | ||
1592 | /** | |
1593 | * Deletes this lesson from the database | |
1594 | */ | |
1595 | public function delete() { | |
1596 | global $CFG, $DB; | |
1597 | require_once($CFG->libdir.'/gradelib.php'); | |
1598 | require_once($CFG->dirroot.'/calendar/lib.php'); | |
1599 | ||
582ba677 FM |
1600 | $cm = get_coursemodule_from_instance('lesson', $this->properties->id, $this->properties->course); |
1601 | $context = context_module::instance($cm->id); | |
1602 | ||
e0e1a83e JMV |
1603 | $this->delete_all_overrides(); |
1604 | ||
0e35ba6f | 1605 | $DB->delete_records("lesson", array("id"=>$this->properties->id)); |
1e7f8ea2 PS |
1606 | $DB->delete_records("lesson_pages", array("lessonid"=>$this->properties->id)); |
1607 | $DB->delete_records("lesson_answers", array("lessonid"=>$this->properties->id)); | |
1608 | $DB->delete_records("lesson_attempts", array("lessonid"=>$this->properties->id)); | |
1609 | $DB->delete_records("lesson_grades", array("lessonid"=>$this->properties->id)); | |
1610 | $DB->delete_records("lesson_timer", array("lessonid"=>$this->properties->id)); | |
1611 | $DB->delete_records("lesson_branch", array("lessonid"=>$this->properties->id)); | |
1e7f8ea2 | 1612 | if ($events = $DB->get_records('event', array("modulename"=>'lesson', "instance"=>$this->properties->id))) { |
70b62308 | 1613 | $coursecontext = context_course::instance($cm->course); |
1e7f8ea2 | 1614 | foreach($events as $event) { |
70b62308 | 1615 | $event->context = $coursecontext; |
e1cd93ce | 1616 | $event = calendar_event::load($event); |
1e7f8ea2 PS |
1617 | $event->delete(); |
1618 | } | |
1619 | } | |
1620 | ||
582ba677 FM |
1621 | // Delete files associated with this module. |
1622 | $fs = get_file_storage(); | |
1623 | $fs->delete_area_files($context->id); | |
1624 | ||
ecea65ca | 1625 | grade_update('mod/lesson', $this->properties->course, 'mod', 'lesson', $this->properties->id, 0, null, array('deleted'=>1)); |
1e7f8ea2 PS |
1626 | return true; |
1627 | } | |
1628 | ||
e0e1a83e JMV |
1629 | /** |
1630 | * Deletes a lesson override from the database and clears any corresponding calendar events | |
1631 | * | |
1632 | * @param int $overrideid The id of the override being deleted | |
1633 | * @return bool true on success | |
1634 | */ | |
1635 | public function delete_override($overrideid) { | |
1636 | global $CFG, $DB; | |
1637 | ||
1638 | require_once($CFG->dirroot . '/calendar/lib.php'); | |
1639 | ||
1640 | $cm = get_coursemodule_from_instance('lesson', $this->properties->id, $this->properties->course); | |
1641 | ||
1642 | $override = $DB->get_record('lesson_overrides', array('id' => $overrideid), '*', MUST_EXIST); | |
1643 | ||
1644 | // Delete the events. | |
1645 | $conds = array('modulename' => 'lesson', | |
1646 | 'instance' => $this->properties->id); | |
1647 | if (isset($override->userid)) { | |
1648 | $conds['userid'] = $override->userid; | |
1649 | } else { | |
1650 | $conds['groupid'] = $override->groupid; | |
1651 | } | |
1652 | $events = $DB->get_records('event', $conds); | |
1653 | foreach ($events as $event) { | |
e1cd93ce | 1654 | $eventold = calendar_event::load($event); |
e0e1a83e JMV |
1655 | $eventold->delete(); |
1656 | } | |
1657 | ||
1658 | $DB->delete_records('lesson_overrides', array('id' => $overrideid)); | |
1659 | ||
1660 | // Set the common parameters for one of the events we will be triggering. | |
1661 | $params = array( | |
1662 | 'objectid' => $override->id, | |
1663 | 'context' => context_module::instance($cm->id), | |
1664 | 'other' => array( | |
1665 | 'lessonid' => $override->lessonid | |
1666 | ) | |
1667 | ); | |
1668 | // Determine which override deleted event to fire. | |
1669 | if (!empty($override->userid)) { | |
1670 | $params['relateduserid'] = $override->userid; | |
1671 | $event = \mod_lesson\event\user_override_deleted::create($params); | |
1672 | } else { | |
1673 | $params['other']['groupid'] = $override->groupid; | |
1674 | $event = \mod_lesson\event\group_override_deleted::create($params); | |
1675 | } | |
1676 | ||
1677 | // Trigger the override deleted event. | |
1678 | $event->add_record_snapshot('lesson_overrides', $override); | |
1679 | $event->trigger(); | |
1680 | ||
1681 | return true; | |
1682 | } | |
1683 | ||
1684 | /** | |
1685 | * Deletes all lesson overrides from the database and clears any corresponding calendar events | |
1686 | */ | |
1687 | public function delete_all_overrides() { | |
1688 | global $DB; | |
1689 | ||
1690 | $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $this->properties->id), 'id'); | |
1691 | foreach ($overrides as $override) { | |
1692 | $this->delete_override($override->id); | |
1693 | } | |
1694 | } | |
1695 | ||
1696 | /** | |
1697 | * Updates the lesson properties with override information for a user. | |
1698 | * | |
1699 | * Algorithm: For each lesson setting, if there is a matching user-specific override, | |
1700 | * then use that otherwise, if there are group-specific overrides, return the most | |
1701 | * lenient combination of them. If neither applies, leave the quiz setting unchanged. | |
1702 | * | |
1703 | * Special case: if there is more than one password that applies to the user, then | |
1704 | * lesson->extrapasswords will contain an array of strings giving the remaining | |
1705 | * passwords. | |
1706 | * | |
1707 | * @param int $userid The userid. | |
1708 | */ | |
1709 | public function update_effective_access($userid) { | |
1710 | global $DB; | |
1711 | ||
1712 | // Check for user override. | |
1713 | $override = $DB->get_record('lesson_overrides', array('lessonid' => $this->properties->id, 'userid' => $userid)); | |
1714 | ||
1715 | if (!$override) { | |
1716 | $override = new stdClass(); | |
1717 | $override->available = null; | |
1718 | $override->deadline = null; | |
1719 | $override->timelimit = null; | |
1720 | $override->review = null; | |
1721 | $override->maxattempts = null; | |
1722 | $override->retake = null; | |
1723 | $override->password = null; | |
1724 | } | |
1725 | ||
1726 | // Check for group overrides. | |
1727 | $groupings = groups_get_user_groups($this->properties->course, $userid); | |
1728 | ||
1729 | if (!empty($groupings[0])) { | |
1730 | // Select all overrides that apply to the User's groups. | |
1731 | list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0])); | |
1732 | $sql = "SELECT * FROM {lesson_overrides} | |
1733 | WHERE groupid $extra AND lessonid = ?"; | |
1734 | $params[] = $this->properties->id; | |
1735 | $records = $DB->get_records_sql($sql, $params); | |
1736 | ||
1737 | // Combine the overrides. | |
1738 | $availables = array(); | |
1739 | $deadlines = array(); | |
1740 | $timelimits = array(); | |
1741 | $reviews = array(); | |
1742 | $attempts = array(); | |
1743 | $retakes = array(); | |
1744 | $passwords = array(); | |
1745 | ||
1746 | foreach ($records as $gpoverride) { | |
1747 | if (isset($gpoverride->available)) { | |
1748 | $availables[] = $gpoverride->available; | |
1749 | } | |
1750 | if (isset($gpoverride->deadline)) { | |
1751 | $deadlines[] = $gpoverride->deadline; | |
1752 | } | |
1753 | if (isset($gpoverride->timelimit)) { | |
1754 | $timelimits[] = $gpoverride->timelimit; | |
1755 | } | |
1756 | if (isset($gpoverride->review)) { | |
1757 | $reviews[] = $gpoverride->review; | |
1758 | } | |
1759 | if (isset($gpoverride->maxattempts)) { | |
1760 | $attempts[] = $gpoverride->maxattempts; | |
1761 | } | |
1762 | if (isset($gpoverride->retake)) { | |
1763 | $retakes[] = $gpoverride->retake; | |
1764 | } | |
1765 | if (isset($gpoverride->password)) { | |
1766 | $passwords[] = $gpoverride->password; | |
1767 | } | |
1768 | } | |
1769 | // If there is a user override for a setting, ignore the group override. | |
1770 | if (is_null($override->available) && count($availables)) { | |
1771 | $override->available = min($availables); | |
1772 | } | |
1773 | if (is_null($override->deadline) && count($deadlines)) { | |
1774 | if (in_array(0, $deadlines)) { | |
1775 | $override->deadline = 0; | |
1776 | } else { | |
1777 | $override->deadline = max($deadlines); | |
1778 | } | |
1779 | } | |
1780 | if (is_null($override->timelimit) && count($timelimits)) { | |
1781 | if (in_array(0, $timelimits)) { | |
1782 | $override->timelimit = 0; | |
1783 | } else { | |
1784 | $override->timelimit = max($timelimits); | |
1785 | } | |
1786 | } | |
1787 | if (is_null($override->review) && count($reviews)) { | |
1788 | $override->review = max($reviews); | |
1789 | } | |
1790 | if (is_null($override->maxattempts) && count($attempts)) { | |
1791 | $override->maxattempts = max($attempts); | |
1792 | } | |
1793 | if (is_null($override->retake) && count($retakes)) { | |
1794 | $override->retake = max($retakes); | |
1795 | } | |
1796 | if (is_null($override->password) && count($passwords)) { | |
1797 | $override->password = array_shift($passwords); | |
1798 | if (count($passwords)) { | |
1799 | $override->extrapasswords = $passwords; | |
1800 | } | |
1801 | } | |
1802 | ||
1803 | } | |
1804 | ||
1805 | // Merge with lesson defaults. | |
1806 | $keys = array('available', 'deadline', 'timelimit', 'maxattempts', 'review', 'retake'); | |
1807 | foreach ($keys as $key) { | |
1808 | if (isset($override->{$key})) { | |
1809 | $this->properties->{$key} = $override->{$key}; | |
1810 | } | |
1811 | } | |
1812 | ||
1813 | // Special handling of lesson usepassword and password. | |
1814 | if (isset($override->password)) { | |
1815 | if ($override->password == '') { | |
1816 | $this->properties->usepassword = 0; | |
1817 | } else { | |
1818 | $this->properties->usepassword = 1; | |
1819 | $this->properties->password = $override->password; | |
1820 | if (isset($override->extrapasswords)) { | |
1821 | $this->properties->extrapasswords = $override->extrapasswords; | |
1822 | } | |
1823 | } | |
1824 | } | |
1825 | } | |
1826 | ||
1e7f8ea2 PS |
1827 | /** |
1828 | * Fetches messages from the session that may have been set in previous page | |
1829 | * actions. | |
1830 | * | |
1831 | * <code> | |
1832 | * // Do not call this method directly instead use | |
1833 | * $lesson->messages; | |
1834 | * </code> | |
1835 | * | |
1836 | * @return array | |
1837 | */ | |
1838 | protected function get_messages() { | |
1839 | global $SESSION; | |
1840 | ||
1841 | $messages = array(); | |
1842 | if (!empty($SESSION->lesson_messages) && is_array($SESSION->lesson_messages) && array_key_exists($this->properties->id, $SESSION->lesson_messages)) { | |
1843 | $messages = $SESSION->lesson_messages[$this->properties->id]; | |
1844 | unset($SESSION->lesson_messages[$this->properties->id]); | |
1845 | } | |
1846 | ||
1847 | return $messages; | |
1848 | } | |
1849 | ||
1850 | /** | |
1851 | * Get all of the attempts for the current user. | |
1852 | * | |
1853 | * @param int $retries | |
1854 | * @param bool $correct Optional: only fetch correct attempts | |
1855 | * @param int $pageid Optional: only fetch attempts at the given page | |
1856 | * @param int $userid Optional: defaults to the current user if not set | |
1857 | * @return array|false | |
1858 | */ | |
1859 | public function get_attempts($retries, $correct=false, $pageid=null, $userid=null) { | |
1860 | global $USER, $DB; | |
1861 | $params = array("lessonid"=>$this->properties->id, "userid"=>$userid, "retry"=>$retries); | |
1862 | if ($correct) { | |
1863 | $params['correct'] = 1; | |
1864 | } | |
1865 | if ($pageid !== null) { | |
1866 | $params['pageid'] = $pageid; | |
1867 | } | |
1868 | if ($userid === null) { | |
1869 | $params['userid'] = $USER->id; | |
1870 | } | |
ef4ba975 | 1871 | return $DB->get_records('lesson_attempts', $params, 'timeseen ASC'); |
1e7f8ea2 PS |
1872 | } |
1873 | ||
b584c49d JL |
1874 | |
1875 | /** | |
1876 | * Get a list of content pages (formerly known as branch tables) viewed in the lesson for the given user during an attempt. | |
1877 | * | |
1878 | * @param int $lessonattempt the lesson attempt number (also known as retries) | |
1879 | * @param int $userid the user id to retrieve the data from | |
1880 | * @param string $sort an order to sort the results in (a valid SQL ORDER BY parameter) | |
1881 | * @param string $fields a comma separated list of fields to return | |
1882 | * @return array of pages | |
1883 | * @since Moodle 3.3 | |
1884 | */ | |
1885 | public function get_content_pages_viewed($lessonattempt, $userid = null, $sort = '', $fields = '*') { | |
1886 | global $USER, $DB; | |
1887 | ||
1888 | if ($userid === null) { | |
1889 | $userid = $USER->id; | |
1890 | } | |
1891 | $conditions = array("lessonid" => $this->properties->id, "userid" => $userid, "retry" => $lessonattempt); | |
1892 | return $DB->get_records('lesson_branch', $conditions, $sort, $fields); | |
1893 | } | |
1894 | ||
1e7f8ea2 PS |
1895 | /** |
1896 | * Returns the first page for the lesson or false if there isn't one. | |
1897 | * | |
1898 | * This method should be called via the magic method __get(); | |
1899 | * <code> | |
1900 | * $firstpage = $lesson->firstpage; | |
1901 | * </code> | |
1902 | * | |
1903 | * @return lesson_page|bool Returns the lesson_page specialised object or false | |
1904 | */ | |
1905 | protected function get_firstpage() { | |
1906 | $pages = $this->load_all_pages(); | |
1907 | if (count($pages) > 0) { | |
1908 | foreach ($pages as $page) { | |
1909 | if ((int)$page->prevpageid === 0) { | |
1910 | return $page; | |
1911 | } | |
1912 | } | |
1913 | } | |
1914 | return false; | |
1915 | } | |
1916 | ||
1917 | /** | |
1918 | * Returns the last page for the lesson or false if there isn't one. | |
1919 | * | |
1920 | * This method should be called via the magic method __get(); | |
1921 | * <code> | |
1922 | * $lastpage = $lesson->lastpage; | |
1923 | * </code> | |
1924 | * | |
1925 | * @return lesson_page|bool Returns the lesson_page specialised object or false | |
1926 | */ | |
1927 | protected function get_lastpage() { | |
1928 | $pages = $this->load_all_pages(); | |
1929 | if (count($pages) > 0) { | |
1930 | foreach ($pages as $page) { | |
1931 | if ((int)$page->nextpageid === 0) { | |
1932 | return $page; | |
1933 | } | |
1934 | } | |
1935 | } | |
1936 | return false; | |
1937 | } | |
1938 | ||
1939 | /** | |
1940 | * Returns the id of the first page of this lesson. (prevpageid = 0) | |
1941 | * @return int | |
1942 | */ | |
1943 | protected function get_firstpageid() { | |
1944 | global $DB; | |
1945 | if ($this->firstpageid == null) { | |
1946 | if (!$this->loadedallpages) { | |
1947 | $firstpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'prevpageid'=>0)); | |
1948 | if (!$firstpageid) { | |
1949 | print_error('cannotfindfirstpage', 'lesson'); | |
1950 | } | |
1951 | $this->firstpageid = $firstpageid; | |
1952 | } else { | |
1953 | $firstpage = $this->get_firstpage(); | |
1954 | $this->firstpageid = $firstpage->id; | |
1955 | } | |
1956 | } | |
1957 | return $this->firstpageid; | |
1958 | } | |
1959 | ||
1960 | /** | |
1961 | * Returns the id of the last page of this lesson. (nextpageid = 0) | |
1962 | * @return int | |
1963 | */ | |
1964 | public function get_lastpageid() { | |
1965 | global $DB; | |
1966 | if ($this->lastpageid == null) { | |
1967 | if (!$this->loadedallpages) { | |
1968 | $lastpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'nextpageid'=>0)); | |
1969 | if (!$lastpageid) { | |
1970 | print_error('cannotfindlastpage', 'lesson'); | |
1971 | } | |
1972 | $this->lastpageid = $lastpageid; | |
1973 | } else { | |
1974 | $lastpageid = $this->get_lastpage(); | |
1975 | $this->lastpageid = $lastpageid->id; | |
1976 | } | |
1977 | } | |
1978 | ||
1979 | return $this->lastpageid; | |
1980 | } | |
1981 | ||
1982 | /** | |
0343553f | 1983 | * Gets the next page id to display after the one that is provided. |
1e7f8ea2 PS |
1984 | * @param int $nextpageid |
1985 | * @return bool | |
1986 | */ | |
1987 | public function get_next_page($nextpageid) { | |
3983f2dc | 1988 | global $USER, $DB; |
1e7f8ea2 PS |
1989 | $allpages = $this->load_all_pages(); |
1990 | if ($this->properties->nextpagedefault) { | |
1991 | // in Flash Card mode...first get number of retakes | |
0343553f | 1992 | $nretakes = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); |
1e7f8ea2 PS |
1993 | shuffle($allpages); |
1994 | $found = false; | |
1995 | if ($this->properties->nextpagedefault == LESSON_UNSEENPAGE) { | |
1996 | foreach ($allpages as $nextpage) { | |
0343553f | 1997 | if (!$DB->count_records("lesson_attempts", array("pageid" => $nextpage->id, "userid" => $USER->id, "retry" => $nretakes))) { |
1e7f8ea2 PS |
1998 | $found = true; |
1999 | break; | |
2000 | } | |
2001 | } | |
2002 | } elseif ($this->properties->nextpagedefault == LESSON_UNANSWEREDPAGE) { | |
2003 | foreach ($allpages as $nextpage) { | |
0343553f | 2004 | if (!$DB->count_records("lesson_attempts", array('pageid' => $nextpage->id, 'userid' => $USER->id, 'correct' => 1, 'retry' => $nretakes))) { |
1e7f8ea2 PS |
2005 | $found = true; |
2006 | break; | |
2007 | } | |
2008 | } | |
2009 | } | |
2010 | if ($found) { | |
2011 | if ($this->properties->maxpages) { | |
2012 | // check number of pages viewed (in the lesson) | |
0343553f RW |
2013 | if ($DB->count_records("lesson_attempts", array("lessonid" => $this->properties->id, "userid" => $USER->id, "retry" => $nretakes)) >= $this->properties->maxpages) { |
2014 | return LESSON_EOL; | |
1e7f8ea2 PS |
2015 | } |
2016 | } | |
0343553f | 2017 | return $nextpage->id; |
1e7f8ea2 PS |
2018 | } |
2019 | } | |
2020 | // In a normal lesson mode | |
2021 | foreach ($allpages as $nextpage) { | |
0343553f RW |
2022 | if ((int)$nextpage->id === (int)$nextpageid) { |
2023 | return $nextpage->id; | |
1e7f8ea2 PS |
2024 | } |
2025 | } | |
0343553f | 2026 | return LESSON_EOL; |
1e7f8ea2 PS |
2027 | } |
2028 | ||
2029 | /** | |
2030 | * Sets a message against the session for this lesson that will displayed next | |
2031 | * time the lesson processes messages | |
2032 | * | |
2033 | * @param string $message | |
2034 | * @param string $class | |
2035 | * @param string $align | |
2036 | * @return bool | |
2037 | */ | |
2038 | public function add_message($message, $class="notifyproblem", $align='center') { | |
2039 | global $SESSION; | |
2040 | ||
2041 | if (empty($SESSION->lesson_messages) || !is_array($SESSION->lesson_messages)) { | |
2042 | $SESSION->lesson_messages = array(); | |
2043 | $SESSION->lesson_messages[$this->properties->id] = array(); | |
2044 | } else if (!array_key_exists($this->properties->id, $SESSION->lesson_messages)) { | |
2045 | $SESSION->lesson_messages[$this->properties->id] = array(); | |
2046 | } | |
2047 | ||
2048 | $SESSION->lesson_messages[$this->properties->id][] = array($message, $class, $align); | |
2049 | ||
2050 | return true; | |
2051 | } | |
2052 | ||
2053 | /** | |
2054 | * Check if the lesson is accessible at the present time | |
2055 | * @return bool True if the lesson is accessible, false otherwise | |
2056 | */ | |
2057 | public function is_accessible() { | |
2058 | $available = $this->properties->available; | |
2059 | $deadline = $this->properties->deadline; | |
2060 | return (($available == 0 || time() >= $available) && ($deadline == 0 || time() < $deadline)); | |
2061 | } | |
2062 | ||
2063 | /** | |
2064 | * Starts the lesson time for the current user | |
2065 | * @return bool Returns true | |
2066 | */ | |
2067 | public function start_timer() { | |
2068 | global $USER, $DB; | |
9c192d81 MN |
2069 | |
2070 | $cm = get_coursemodule_from_instance('lesson', $this->properties()->id, $this->properties()->course, | |
2071 | false, MUST_EXIST); | |
2072 | ||
2073 | // Trigger lesson started event. | |
2074 | $event = \mod_lesson\event\lesson_started::create(array( | |
2075 | 'objectid' => $this->properties()->id, | |
2076 | 'context' => context_module::instance($cm->id), | |
2077 | 'courseid' => $this->properties()->course | |
2078 | )); | |
2079 | $event->trigger(); | |
2080 | ||
1e7f8ea2 | 2081 | $USER->startlesson[$this->properties->id] = true; |
87e472bd JL |
2082 | |
2083 | $timenow = time(); | |
1e7f8ea2 PS |
2084 | $startlesson = new stdClass; |
2085 | $startlesson->lessonid = $this->properties->id; | |
2086 | $startlesson->userid = $USER->id; | |
87e472bd JL |
2087 | $startlesson->starttime = $timenow; |
2088 | $startlesson->lessontime = $timenow; | |
2089 | if (WS_SERVER) { | |
2090 | $startlesson->timemodifiedoffline = $timenow; | |
2091 | } | |
1e7f8ea2 | 2092 | $DB->insert_record('lesson_timer', $startlesson); |
a1acc001 JMV |
2093 | if ($this->properties->timelimit) { |
2094 | $this->add_message(get_string('timelimitwarning', 'lesson', format_time($this->properties->timelimit)), 'center'); | |
1e7f8ea2 PS |
2095 | } |
2096 | return true; | |
2097 | } | |
2098 | ||
2099 | /** | |
2100 | * Updates the timer to the current time and returns the new timer object | |
2101 | * @param bool $restart If set to true the timer is restarted | |
2102 | * @param bool $continue If set to true AND $restart=true then the timer | |
2103 | * will continue from a previous attempt | |
2104 | * @return stdClass The new timer | |
2105 | */ | |
25345cb4 | 2106 | public function update_timer($restart=false, $continue=false, $endreached =false) { |
1e7f8ea2 | 2107 | global $USER, $DB; |
e0f7b963 SB |
2108 | |
2109 | $cm = get_coursemodule_from_instance('lesson', $this->properties->id, $this->properties->course); | |
2110 | ||
1e7f8ea2 PS |
2111 | // clock code |
2112 | // get time information for this user | |
592c94f3 | 2113 | if (!$timer = $this->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1)) { |
5ca2e765 | 2114 | $this->start_timer(); |
592c94f3 | 2115 | $timer = $this->get_user_timers($USER->id, 'starttime DESC', '*', 0, 1); |
1e7f8ea2 | 2116 | } |
5ca2e765 | 2117 | $timer = current($timer); // This will get the latest start time record. |
1e7f8ea2 PS |
2118 | |
2119 | if ($restart) { | |
2120 | if ($continue) { | |
2121 | // continue a previous test, need to update the clock (think this option is disabled atm) | |
2122 | $timer->starttime = time() - ($timer->lessontime - $timer->starttime); | |
e0f7b963 SB |
2123 | |
2124 | // Trigger lesson resumed event. | |
2125 | $event = \mod_lesson\event\lesson_resumed::create(array( | |
2126 | 'objectid' => $this->properties->id, | |
2127 | 'context' => context_module::instance($cm->id), | |
2128 | 'courseid' => $this->properties->course | |
2129 | )); | |
2130 | $event->trigger(); | |
2131 | ||
1e7f8ea2 PS |
2132 | } else { |
2133 | // starting over, so reset the clock | |
2134 | $timer->starttime = time(); | |
e0f7b963 SB |
2135 | |
2136 | // Trigger lesson restarted event. | |
2137 | $event = \mod_lesson\event\lesson_restarted::create(array( | |
2138 | 'objectid' => $this->properties->id, | |
2139 | 'context' => context_module::instance($cm->id), | |
2140 | 'courseid' => $this->properties->course | |
2141 | )); | |
2142 | $event->trigger(); | |
2143 | ||
1e7f8ea2 PS |
2144 | } |
2145 | } | |
2146 | ||
87e472bd JL |
2147 | $timenow = time(); |
2148 | $timer->lessontime = $timenow; | |
2149 | if (WS_SERVER) { | |
2150 | $timer->timemodifiedoffline = $timenow; | |
2151 | } | |
25345cb4 | 2152 | $timer->completed = $endreached; |
1e7f8ea2 | 2153 | $DB->update_record('lesson_timer', $timer); |
4b570f71 JMV |
2154 | |
2155 | // Update completion state. | |
2156 | $cm = get_coursemodule_from_instance('lesson', $this->properties()->id, $this->properties()->course, | |
2157 | false, MUST_EXIST); | |
2158 | $course = get_course($cm->course); | |
2159 | $completion = new completion_info($course); | |
2160 | if ($completion->is_enabled($cm) && $this->properties()->completiontimespent > 0) { | |
2161 | $completion->update_state($cm, COMPLETION_COMPLETE); | |
2162 | } | |
1e7f8ea2 PS |
2163 | return $timer; |
2164 | } | |
2165 | ||
2166 | /** | |
2167 | * Updates the timer to the current time then stops it by unsetting the user var | |
2168 | * @return bool Returns true | |
2169 | */ | |
2170 | public function stop_timer() { | |
2171 | global $USER, $DB; | |
2172 | unset($USER->startlesson[$this->properties->id]); | |
00c027c7 MN |
2173 | |
2174 | $cm = get_coursemodule_from_instance('lesson', $this->properties()->id, $this->properties()->course, | |
2175 | false, MUST_EXIST); | |
2176 | ||
2177 | // Trigger lesson ended event. | |
2178 | $event = \mod_lesson\event\lesson_ended::create(array( | |
2179 | 'objectid' => $this->properties()->id, | |
2180 | 'context' => context_module::instance($cm->id), | |
2181 | 'courseid' => $this->properties()->course | |
2182 | )); | |
2183 | $event->trigger(); | |
2184 | ||
25345cb4 | 2185 | return $this->update_timer(false, false, true); |
1e7f8ea2 PS |
2186 | } |
2187 | ||
2188 | /** | |
2189 | * Checks to see if the lesson has pages | |
2190 | */ | |
2191 | public function has_pages() { | |
2192 | global $DB; | |
2193 | $pagecount = $DB->count_records('lesson_pages', array('lessonid'=>$this->properties->id)); | |
2194 | return ($pagecount>0); | |
2195 | } | |
2196 | ||
2197 | /** | |
2198 | * Returns the link for the related activity | |
2199 | * @return array|false | |
2200 | */ | |
2201 | public function link_for_activitylink() { | |
2202 | global $DB; | |
2203 | $module = $DB->get_record('course_modules', array('id' => $this->properties->activitylink)); | |
2204 | if ($module) { | |
2205 | $modname = $DB->get_field('modules', 'name', array('id' => $module->module)); | |
2206 | if ($modname) { | |
2207 | $instancename = $DB->get_field($modname, 'name', array('id' => $module->instance)); | |
2208 | if ($instancename) { | |
2209 | return html_writer::link(new moodle_url('/mod/'.$modname.'/view.php', array('id'=>$this->properties->activitylink)), | |
560f5495 | 2210 | get_string('activitylinkname', 'lesson', $instancename), |
1e7f8ea2 PS |
2211 | array('class'=>'centerpadded lessonbutton standardbutton')); |
2212 | } | |
2213 | } | |
2214 | } | |
2215 | return ''; | |
2216 | } | |
2217 | ||
2218 | /** | |
2219 | * Loads the requested page. | |
2220 | * | |
2221 | * This function will return the requested page id as either a specialised | |
2222 | * lesson_page object OR as a generic lesson_page. | |
2223 | * If the page has been loaded previously it will be returned from the pages | |
2224 | * array, otherwise it will be loaded from the database first | |
2225 | * | |
2226 | * @param int $pageid | |
2227 | * @return lesson_page A lesson_page object or an object that extends it | |
2228 | */ | |
2229 | public function load_page($pageid) { | |
2230 | if (!array_key_exists($pageid, $this->pages)) { | |
2231 | $manager = lesson_page_type_manager::get($this); | |
2232 | $this->pages[$pageid] = $manager->load_page($pageid, $this); | |
2233 | } | |
2234 | return $this->pages[$pageid]; | |
2235 | } | |
2236 | ||
2237 | /** | |
2238 | * Loads ALL of the pages for this lesson | |
2239 | * | |
2240 | * @return array An array containing all pages from this lesson | |
2241 | */ | |
2242 | public function load_all_pages() { | |
2243 | if (!$this->loadedallpages) { | |
2244 | $manager = lesson_page_type_manager::get($this); | |
2245 | $this->pages = $manager->load_all_pages($this); | |
2246 | $this->loadedallpages = true; | |
2247 | } | |
2248 | return $this->pages; | |
2249 | } | |
2250 | ||
9f937c3b AG |
2251 | /** |
2252 | * Duplicate the lesson page. | |
2253 | * | |
2254 | * @param int $pageid Page ID of the page to duplicate. | |
2255 | * @return void. | |
2256 | */ | |
2257 | public function duplicate_page($pageid) { | |
2258 | global $PAGE; | |
2259 | $cm = get_coursemodule_from_instance('lesson', $this->properties->id, $this->properties->course); | |
2260 | $context = context_module::instance($cm->id); | |
2261 | // Load the page. | |
2262 | $page = $this->load_page($pageid); | |
2263 | $properties = $page->properties(); | |
2264 | // The create method checks to see if these properties are set and if not sets them to zero, hence the unsetting here. | |
2265 | if (!$properties->qoption) { | |
2266 | unset($properties->qoption); | |
2267 | } | |
2268 | if (!$properties->layout) { | |
2269 | unset($properties->layout); | |
2270 | } | |
2271 | if (!$properties->display) { | |
2272 | unset($properties->display); | |
2273 | } | |
2274 | ||
2275 | $properties->pageid = $pageid; | |
2276 | // Add text and format into the format required to create a new page. | |
2277 | $properties->contents_editor = array( | |
2278 | 'text' => $properties->contents, | |
2279 | 'format' => $properties->contentsformat | |
2280 | ); | |
2281 | $answers = $page->get_answers(); | |
2282 | // Answers need to be added to $properties. | |
2283 | $i = 0; | |
2284 | $answerids = array(); | |
2285 | foreach ($answers as $answer) { | |
2286 | // Needs to be rearranged to work with the create function. | |
2287 | $properties->answer_editor[$i] = array( | |
2288 | 'text' => $answer->answer, | |
2289 | 'format' => $answer->answerformat | |
2290 | ); | |
2291 | ||
2292 | $properties->response_editor[$i] = array( | |
2293 | 'text' => $answer->response, | |
2294 | 'format' => $answer->responseformat | |
2295 | ); | |
2296 | $answerids[] = $answer->id; | |
2297 | ||
2298 | $properties->jumpto[$i] = $answer->jumpto; | |
2299 | $properties->score[$i] = $answer->score; | |
2300 | ||
2301 | $i++; | |
2302 | } | |
2303 | // Create the duplicate page. | |
2304 | $newlessonpage = lesson_page::create($properties, $this, $context, $PAGE->course->maxbytes); | |
2305 | $newanswers = $newlessonpage->get_answers(); | |
2306 | // Copy over the file areas as well. | |
2307 | $this->copy_page_files('page_contents', $pageid, $newlessonpage->id, $context->id); | |
2308 | $j = 0; | |
2309 | foreach ($newanswers as $answer) { | |
f4eef5fb | 2310 | if (isset($answer->answer) && strpos($answer->answer, '@@PLUGINFILE@@') !== false) { |
9f937c3b AG |
2311 | $this->copy_page_files('page_answers', $answerids[$j], $answer->id, $context->id); |
2312 | } | |
f4eef5fb | 2313 | if (isset($answer->response) && !is_array($answer->response) && strpos($answer->response, '@@PLUGINFILE@@') !== false) { |
9f937c3b AG |
2314 | $this->copy_page_files('page_responses', $answerids[$j], $answer->id, $context->id); |
2315 | } | |
2316 | $j++; | |
2317 | } | |
2318 | } | |
2319 | ||
2320 | /** | |
2321 | * Copy the files from one page to another. | |
2322 | * | |
2323 | * @param string $filearea Area that the files are stored. | |
2324 | * @param int $itemid Item ID. | |
2325 | * @param int $newitemid The item ID for the new page. | |
2326 | * @param int $contextid Context ID for this page. | |
2327 | * @return void. | |
2328 | */ | |
2329 | protected function copy_page_files($filearea, $itemid, $newitemid, $contextid) { | |
2330 | $fs = get_file_storage(); | |
2331 | $files = $fs->get_area_files($contextid, 'mod_lesson', $filearea, $itemid); | |
2332 | foreach ($files as $file) { | |
2333 | $fieldupdates = array('itemid' => $newitemid); | |
2334 | $fs->create_file_from_storedfile($fieldupdates, $file); | |
2335 | } | |
2336 | } | |
2337 | ||
1e7f8ea2 | 2338 | /** |
ff85f902 | 2339 | * Determines if a jumpto value is correct or not. |
1e7f8ea2 PS |
2340 | * |
2341 | * returns true if jumpto page is (logically) after the pageid page or | |
2342 | * if the jumpto value is a special value. Returns false in all other cases. | |
2343 | * | |
2344 | * @param int $pageid Id of the page from which you are jumping from. | |
2345 | * @param int $jumpto The jumpto number. | |
2346 | * @return boolean True or false after a series of tests. | |
2347 | **/ | |
2348 | public function jumpto_is_correct($pageid, $jumpto) { | |
2349 | global $DB; | |
2350 | ||
2351 | // first test the special values | |
2352 | if (!$jumpto) { | |
2353 | // same page | |
2354 | return false; | |
2355 | } elseif ($jumpto == LESSON_NEXTPAGE) { | |
2356 | return true; | |
2357 | } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) { | |
2358 | return true; | |
2359 | } elseif ($jumpto == LESSON_RANDOMPAGE) { | |
2360 | return true; | |
2361 | } elseif ($jumpto == LESSON_CLUSTERJUMP) { | |
2362 | return true; | |
2363 | } elseif ($jumpto == LESSON_EOL) { | |
2364 | return true; | |
2365 | } | |
2366 | ||
2367 | $pages = $this->load_all_pages(); | |
2368 | $apageid = $pages[$pageid]->nextpageid; | |
2369 | while ($apageid != 0) { | |
2370 | if ($jumpto == $apageid) { | |
2371 | return true; | |
2372 | } | |
2373 | $apageid = $pages[$apageid]->nextpageid; | |
2374 | } | |
2375 | return false; | |
2376 | } | |
2377 | ||
2378 | /** | |
2379 | * Returns the time a user has remaining on this lesson | |
2380 | * @param int $starttime Starttime timestamp | |
2381 | * @return string | |
2382 | */ | |
2383 | public function time_remaining($starttime) { | |
e0e1a83e | 2384 | $timeleft = $starttime + $this->properties->timelimit - time(); |
1e7f8ea2 PS |
2385 | $hours = floor($timeleft/3600); |
2386 | $timeleft = $timeleft - ($hours * 3600); | |
2387 | $minutes = floor($timeleft/60); | |
2388 | $secs = $timeleft - ($minutes * 60); | |
2389 | ||
2390 | if ($minutes < 10) { | |
2391 | $minutes = "0$minutes"; | |
2392 | } | |
2393 | if ($secs < 10) { | |
2394 | $secs = "0$secs"; | |
2395 | } | |
2396 | $output = array(); | |
2397 | $output[] = $hours; | |
2398 | $output[] = $minutes; | |
2399 | $output[] = $secs; | |
2400 | $output = implode(':', $output); | |
2401 | return $output; | |
2402 | } | |
2403 | ||
2404 | /** | |
2405 | * Interprets LESSON_CLUSTERJUMP jumpto value. | |
2406 | * | |
2407 | * This will select a page randomly | |
ff85f902 | 2408 | * and the page selected will be inbetween a cluster page and end of clutter or end of lesson |
1e7f8ea2 PS |
2409 | * and the page selected will be a page that has not been viewed already |
2410 | * and if any pages are within a branch table or end of branch then only 1 page within | |
2411 | * the branch table or end of branch will be randomly selected (sub clustering). | |
2412 | * | |
2413 | * @param int $pageid Id of the current page from which we are jumping from. | |
2414 | * @param int $userid Id of the user. | |
2415 | * @return int The id of the next page. | |
2416 | **/ | |
2417 | public function cluster_jump($pageid, $userid=null) { | |
2418 | global $DB, $USER; | |
2419 | ||
2420 | if ($userid===null) { | |
2421 | $userid = $USER->id; | |
2422 | } | |
2423 | // get the number of retakes | |
2424 | if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$userid))) { | |
2425 | $retakes = 0; | |
2426 | } | |
2427 | // get all the lesson_attempts aka what the user has seen | |
2428 | $seenpages = array(); | |
2429 | if ($attempts = $this->get_attempts($retakes)) { | |
2430 | foreach ($attempts as $attempt) { | |
2431 | $seenpages[$attempt->pageid] = $attempt->pageid; | |
2432 | } | |
2433 | ||
2434 | } | |
2435 | ||
2436 | // get the lesson pages | |
2437 | $lessonpages = $this->load_all_pages(); | |
2438 | // find the start of the cluster | |
2439 | while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page | |
2440 | if ($lessonpages[$pageid]->qtype == LESSON_PAGE_CLUSTER) { | |
2441 | break; | |
2442 | } | |
2443 | $pageid = $lessonpages[$pageid]->prevpageid; | |
2444 | } | |
2445 | ||
2446 | $clusterpages = array(); | |
2447 | $clusterpages = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_ENDOFCLUSTER)); | |
2448 | $unseen = array(); | |
2449 | foreach ($clusterpages as $key=>$cluster) { | |
fc16a1ac JMV |
2450 | // Remove the page if it is in a branch table or is an endofbranch. |
2451 | if ($this->is_sub_page_of_type($cluster->id, | |
2452 | array(LESSON_PAGE_BRANCHTABLE), array(LESSON_PAGE_ENDOFBRANCH, LESSON_PAGE_CLUSTER)) | |
2453 | || $cluster->qtype == LESSON_PAGE_ENDOFBRANCH) { | |
1e7f8ea2 | 2454 | unset($clusterpages[$key]); |
fc16a1ac JMV |
2455 | } else if ($cluster->qtype == LESSON_PAGE_BRANCHTABLE) { |
2456 | // If branchtable, check to see if any pages inside have been viewed. | |
2457 | $branchpages = $this->get_sub_pages_of($cluster->id, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH)); | |
2458 | $flag = true; | |
2459 | foreach ($branchpages as $branchpage) { | |
2460 | if (array_key_exists($branchpage->id, $seenpages)) { // Check if any of the pages have been viewed. | |
2461 | $flag = false; | |
2462 | } | |
2463 | } | |
2464 | if ($flag && count($branchpages) > 0) { | |
2465 | // Add branch table. | |
2466 | $unseen[] = $cluster; | |
2467 | } | |
1e7f8ea2 PS |
2468 | } elseif ($cluster->is_unseen($seenpages)) { |
2469 | $unseen[] = $cluster; | |
2470 | } | |
2471 | } | |
2472 | ||
2473 | if (count($unseen) > 0) { | |
2474 | // it does not contain elements, then use exitjump, otherwise find out next page/branch | |
2475 | $nextpage = $unseen[rand(0, count($unseen)-1)]; | |
2476 | if ($nextpage->qtype == LESSON_PAGE_BRANCHTABLE) { | |
2477 | // if branch table, then pick a random page inside of it | |
2478 | $branchpages = $this->get_sub_pages_of($nextpage->id, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH)); | |
2479 | return $branchpages[rand(0, count($branchpages)-1)]->id; | |
2480 | } else { // otherwise, return the page's id | |
2481 | return $nextpage->id; | |
2482 | } | |
2483 | } else { | |
2484 | // seen all there is to see, leave the cluster | |
2485 | if (end($clusterpages)->nextpageid == 0) { | |
2486 | return LESSON_EOL; | |
2487 | } else { | |
2488 | $clusterendid = $pageid; | |
a30e935a JMV |
2489 | while ($clusterendid != 0) { // This condition should not be satisfied... should be an end of cluster page. |
2490 | if ($lessonpages[$clusterendid]->qtype == LESSON_PAGE_ENDOFCLUSTER) { | |
1e7f8ea2 PS |
2491 | break; |
2492 | } | |
a30e935a | 2493 | $clusterendid = $lessonpages[$clusterendid]->nextpageid; |
1e7f8ea2 PS |
2494 | } |
2495 | $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $clusterendid, "lessonid" => $this->properties->id)); | |
2496 | if ($exitjump == LESSON_NEXTPAGE) { | |
a30e935a | 2497 | $exitjump = $lessonpages[$clusterendid]->nextpageid; |
1e7f8ea2 PS |
2498 | } |
2499 | if ($exitjump == 0) { | |
2500 | return LESSON_EOL; | |
2501 | } else if (in_array($exitjump, array(LESSON_EOL, LESSON_PREVIOUSPAGE))) { | |
2502 | return $exitjump; | |
2503 | } else { | |
2504 | if (!array_key_exists($exitjump, $lessonpages)) { | |
2505 | $found = false; | |
2506 | foreach ($lessonpages as $page) { | |
2507 | if ($page->id === $clusterendid) { | |
2508 | $found = true; | |
2509 | } else if ($page->qtype == LESSON_PAGE_ENDOFCLUSTER) { | |
2510 | $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $page->id, "lessonid" => $this->properties->id)); | |
0d4705c7 JMV |
2511 | if ($exitjump == LESSON_NEXTPAGE) { |
2512 | $exitjump = $lessonpages[$page->id]->nextpageid; | |
2513 | } | |
1e7f8ea2 PS |
2514 | break; |
2515 | } | |
2516 | } | |
2517 | } | |
2518 | if (!array_key_exists($exitjump, $lessonpages)) { | |
2519 | return LESSON_EOL; | |
2520 | } | |
2521 | return $exitjump; | |
2522 | } | |
2523 | } | |
2524 | } | |
2525 | } | |
2526 | ||
2527 | /** | |
2528 | * Finds all pages that appear to be a subtype of the provided pageid until | |
2529 | * an end point specified within $ends is encountered or no more pages exist | |
2530 | * | |
2531 | * @param int $pageid | |
2532 | * @param array $ends An array of LESSON_PAGE_* types that signify an end of | |
2533 | * the subtype | |
2534 | * @return array An array of specialised lesson_page objects | |
2535 | */ | |
2536 | public function get_sub_pages_of($pageid, array $ends) { | |
2537 | $lessonpages = $this->load_all_pages(); | |
2538 | $pageid = $lessonpages[$pageid]->nextpageid; // move to the first page after the branch table | |
2539 | $pages = array(); | |
2540 | ||
2541 | while (true) { | |
2542 | if ($pageid == 0 || in_array($lessonpages[$pageid]->qtype, $ends)) { | |
2543 | break; | |
2544 | } | |
2545 | $pages[] = $lessonpages[$pageid]; | |
2546 | $pageid = $lessonpages[$pageid]->nextpageid; | |
2547 | } | |
2548 | ||
2549 | return $pages; | |
2550 | } | |
2551 | ||
2552 | /** | |
2553 | * Checks to see if the specified page[id] is a subpage of a type specified in | |
2554 | * the $types array, until either there are no more pages of we find a type | |
ff85f902 | 2555 | * corresponding to that of a type specified in $ends |
1e7f8ea2 PS |
2556 | * |
2557 | * @param int $pageid The id of the page to check | |
2558 | * @param array $types An array of types that would signify this page was a subpage | |
2559 | * @param array $ends An array of types that mean this is not a subpage | |
2560 | * @return bool | |
2561 | */ | |
2562 | public function is_sub_page_of_type($pageid, array $types, array $ends) { | |
2563 | $pages = $this->load_all_pages(); | |
2564 | $pageid = $pages[$pageid]->prevpageid; // move up one | |
2565 | ||
2566 | array_unshift($ends, 0); | |
2567 | // go up the pages till branch table | |
2568 | while (true) { | |
2569 | if ($pageid==0 || in_array($pages[$pageid]->qtype, $ends)) { | |
2570 | return false; | |
2571 | } else if (in_array($pages[$pageid]->qtype, $types)) { | |
2572 | return true; | |
2573 | } | |
2574 | $pageid = $pages[$pageid]->prevpageid; | |
2575 | } | |
2576 | } | |
b7bfbfee DM |
2577 | |
2578 | /** | |
2579 | * Move a page resorting all other pages. | |
2580 | * | |
2581 | * @param int $pageid | |
2582 | * @param int $after | |
2583 | * @return void | |
2584 | */ | |
2585 | public function resort_pages($pageid, $after) { | |
2586 | global $CFG; | |
2587 | ||
2588 | $cm = get_coursemodule_from_instance('lesson', $this->properties->id, $this->properties->course); | |
2589 | $context = context_module::instance($cm->id); | |
2590 | ||
2591 | $pages = $this->load_all_pages(); | |
2592 | ||
2593 | if (!array_key_exists($pageid, $pages) || ($after!=0 && !array_key_exists($after, $pages))) { | |
2594 | print_error('cannotfindpages', 'lesson', "$CFG->wwwroot/mod/lesson/edit.php?id=$cm->id"); | |
2595 | } | |
2596 | ||
2597 | $pagetomove = clone($pages[$pageid]); | |
2598 | unset($pages[$pageid]); | |
2599 | ||
2600 | $pageids = array(); | |
2601 | if ($after === 0) { | |
2602 | $pageids['p0'] = $pageid; | |
2603 | } | |
2604 | foreach ($pages as $page) { | |
2605 | $pageids[] = $page->id; | |
2606 | if ($page->id == $after) { | |
2607 | $pageids[] = $pageid; | |
2608 | } | |
2609 | } | |
2610 | ||
2611 | $pageidsref = $pageids; | |
2612 | reset($pageidsref); | |
2613 | $prev = 0; | |
2614 | $next = next($pageidsref); | |
2615 | foreach ($pageids as $pid) { | |
2616 | if ($pid === $pageid) { | |
2617 | $page = $pagetomove; | |
2618 | } else { | |
2619 | $page = $pages[$pid]; | |
2620 | } | |
2621 | if ($page->prevpageid != $prev || $page->nextpageid != $next) { | |
2622 | $page->move($next, $prev); | |
2623 | ||
2624 | if ($pid === $pageid) { | |
2625 | // We will trigger an event. | |
2626 | $pageupdated = array('next' => $next, 'prev' => $prev); | |
2627 | } | |
2628 | } | |
2629 | ||
2630 | $prev = $page->id; | |
2631 | $next = next($pageidsref); | |
2632 | if (!$next) { | |
2633 | $next = 0; | |
2634 | } | |
2635 | } | |
2636 | ||
2637 | // Trigger an event: page moved. | |
2638 | if (!empty($pageupdated)) { | |
2639 | $eventparams = array( | |
2640 | 'context' => $context, | |
2641 | 'objectid' => $pageid, | |
2642 | 'other' => array( | |
2643 | 'pagetype' => $page->get_typestring(), | |
2644 | 'prevpageid' => $pageupdated['prev'], | |
2645 | 'nextpageid' => $pageupdated['next'] | |
2646 | ) | |
2647 | ); | |
2648 | $event = \mod_lesson\event\page_moved::create($eventparams); | |
2649 | $event->trigger(); | |
2650 | } | |
2651 | ||
2652 | } | |
4a3391b6 JL |
2653 | |
2654 | /** | |
2655 | * Return the lesson context object. | |
2656 | * | |
2657 | * @return stdClass context | |
2658 | * @since Moodle 3.3 | |
2659 | */ | |
2660 | public function get_context() { | |
2661 | if ($this->context == null) { | |
2662 | $this->context = context_module::instance($this->get_cm()->id); | |
2663 | } | |
2664 | return $this->context; | |
2665 | } | |
2666 | ||
2667 | /** | |
2668 | * Set the lesson course module object. | |
2669 | * | |
2670 | * @param stdClass $cm course module objct | |
2671 | * @since Moodle 3.3 | |
2672 | */ | |
2673 | private function set_cm($cm) { | |
2674 | $this->cm = $cm; | |
2675 | } | |
2676 | ||
2677 | /** | |
2678 | * Return the lesson course module object. | |
2679 | * | |
2680 | * @return stdClass course module | |
2681 | * @since Moodle 3.3 | |
2682 | */ | |
2683 | public function get_cm() { | |
2684 | if ($this->cm == null) { | |
2685 | $this->cm = get_coursemodule_from_instance('lesson', $this->properties->id); | |
2686 | } | |
2687 | return $this->cm; | |
2688 | } | |
2689 | ||
7d7a2a4e JL |
2690 | /** |
2691 | * Set the lesson course object. | |
2692 | * | |
2693 | * @param stdClass $course course objct | |
2694 | * @since Moodle 3.3 | |
2695 | */ | |
2696 | private function set_courserecord($course) { | |
2697 | $this->courserecord = $course; | |
2698 | } | |
2699 | ||
2700 | /** | |
2701 | * Return the lesson course object. | |
2702 | * | |
2703 | * @return stdClass course | |
2704 | * @since Moodle 3.3 | |
2705 | */ | |
2706 | public function get_courserecord() { | |
2707 | global $DB; | |
2708 | ||
2709 | if ($this->courserecord == null) { | |
2710 | $this->courserecord = $DB->get_record('course', array('id' => $this->properties->course)); | |
2711 | } | |
2712 | return $this->courserecord; | |
2713 | } | |
2714 | ||
4a3391b6 JL |
2715 | /** |
2716 | * Check if the user can manage the lesson activity. | |
2717 | * | |
2718 | * @return bool true if the user can manage the lesson | |
2719 | * @since Moodle 3.3 | |
2720 | */ | |
2721 | public function can_manage() { | |
2722 | return has_capability('mod/lesson:manage', $this->get_context()); | |
2723 | } | |
2724 | ||
2725 | /** | |
2726 | * Check if time restriction is applied. | |
2727 | * | |
2728 | * @return mixed false if there aren't restrictions or an object with the restriction information | |
2729 | * @since Moodle 3.3 | |
2730 | */ | |
2731 | public function get_time_restriction_status() { | |
2732 | if ($this->can_manage()) { | |
2733 | return false; | |
2734 | } | |
2735 | ||
2736 | if (!$this->is_accessible()) { | |
2737 | if ($this->properties->deadline != 0 && time() > $this->properties->deadline) { | |
2738 | $status = ['reason' => 'lessonclosed', 'time' => $this->properties->deadline]; | |
2739 | } else { | |
2740 | $status = ['reason' => 'lessonopen', 'time' => $this->properties->available]; | |
2741 | } | |
2742 | return (object) $status; | |
2743 | } | |
2744 | return false; | |
2745 | } | |
2746 | ||
2747 | /** | |
2748 | * Check if password restriction is applied. | |
2749 | * | |
2750 | * @param string $userpassword the user password to check (if the restriction is set) | |
2751 | * @return mixed false if there aren't restrictions or an object with the restriction information | |
2752 | * @since Moodle 3.3 | |
2753 | */ | |
2754 | public function get_password_restriction_status($userpassword) { | |
2755 | global $USER; | |
2756 | if ($this->can_manage()) { | |
2757 | return false; | |
2758 | } | |
2759 | ||
2760 | if ($this->properties->usepassword && empty($USER->lessonloggedin[$this->id])) { | |
2761 | $correctpass = false; | |
2762 | if (!empty($userpassword) && | |
2763 | (($this->properties->password == md5(trim($userpassword))) || ($this->properties->password == trim($userpassword)))) { | |
2764 | // With or without md5 for backward compatibility (MDL-11090). | |
2765 | $correctpass = true; | |
2766 | $USER->lessonloggedin[$this->id] = true; | |
2767 | } else if (isset($this->properties->extrapasswords)) { | |
2768 | // Group overrides may have additional passwords. | |
2769 | foreach ($this->properties->extrapasswords as $password) { | |
2770 | if (strcmp($password, md5(trim($userpassword))) === 0 || strcmp($password, trim($userpassword)) === 0) { | |
2771 | $correctpass = true; | |
2772 | $USER->lessonloggedin[$this->id] = true; | |
2773 | } | |
2774 | } | |
2775 | } | |
2776 | return !$correctpass; | |
2777 | } | |
2778 | return false; | |
2779 | } | |
2780 | ||
2781 | /** | |
2782 | * Check if dependencies restrictions are applied. | |
2783 | * | |
2784 | * @return mixed false if there aren't restrictions or an object with the restriction information | |
2785 | * @since Moodle 3.3 | |
2786 | */ | |
2787 | public function get_dependencies_restriction_status() { | |
2788 | global $DB, $USER; | |
2789 | if ($this->can_manage()) { | |
2790 | return false; | |
2791 | } | |
2792 | ||
2793 | if ($dependentlesson = $DB->get_record('lesson', array('id' => $this->properties->dependency))) { | |
2794 | // Lesson exists, so we can proceed. | |
2795 | $conditions = unserialize($this->properties->conditions); | |
2796 | // Assume false for all. | |
2797 | $errors = array(); | |
2798 | // Check for the timespent condition. | |
2799 | if ($conditions->timespent) { | |
2800 | $timespent = false; | |
2801 | if ($attempttimes = $DB->get_records('lesson_timer', array("userid" => $USER->id, "lessonid" => $dependentlesson->id))) { | |
2802 | // Go through all the times and test to see if any of them satisfy the condition. | |
2803 | foreach ($attempttimes as $attempttime) { | |
2804 | $duration = $attempttime->lessontime - $attempttime->starttime; | |
2805 | if ($conditions->timespent < $duration / 60) { | |
2806 | $timespent = true; | |
2807 | } | |
2808 | } | |
2809 | } | |
2810 | if (!$timespent) { | |
2811 | $errors[] = get_string('timespenterror', 'lesson', $conditions->timespent); | |
2812 | } | |
2813 | } | |
2814 | // Check for the gradebetterthan condition. | |
2815 | if ($conditions->gradebetterthan) { | |
2816 | $gradebetterthan = false; | |
2817 | if ($studentgrades = $DB->get_records('lesson_grades', array("userid" => $USER->id, "lessonid" => $dependentlesson->id))) { | |
2818 | // Go through all the grades and test to see if any of them satisfy the condition. | |
2819 | foreach ($studentgrades as $studentgrade) { | |
2820 | if ($studentgrade->grade >= $conditions->gradebetterthan) { | |
2821 | $gradebetterthan = true; | |
2822 | } | |
2823 | } | |
2824 | } | |
2825 | if (!$gradebetterthan) { | |
2826 | $errors[] = get_string('gradebetterthanerror', 'lesson', $conditions->gradebetterthan); | |
2827 | } | |
2828 | } | |
2829 | // Check for the completed condition. | |
2830 | if ($conditions->completed) { | |
2831 | if (!$DB->count_records('lesson_grades', array('userid' => $USER->id, 'lessonid' => $dependentlesson->id))) { | |
2832 | $errors[] = get_string('completederror', 'lesson'); | |
2833 | } | |
2834 | } | |
2835 | if (!empty($errors)) { | |
2836 | return (object) ['errors' => $errors, 'dependentlesson' => $dependentlesson]; | |
2837 | } | |
2838 | } | |
2839 | return false; | |
2840 | } | |
37029e46 JL |
2841 | |
2842 | /** | |
2843 | * Check if the lesson is in review mode. (The user already finished it and retakes are not allowed). | |
2844 | * | |
2845 | * @return bool true if is in review mode | |
2846 | * @since Moodle 3.3 | |
2847 | */ | |
2848 | public function is_in_review_mode() { | |
2849 | global $DB, $USER; | |
2850 | ||
2851 | $userhasgrade = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); | |
2852 | if ($userhasgrade && !$this->properties->retake) { | |
2853 | return true; | |
2854 | } | |
2855 | return false; | |
2856 | } | |
2857 | ||
2858 | /** | |
2859 | * Return the last page the current user saw. | |
2860 | * | |
2861 | * @param int $retriescount the number of retries for the lesson (the last retry number). | |
2862 | * @return mixed false if the user didn't see the lesson or the last page id | |
2863 | */ | |
2864 | public function get_last_page_seen($retriescount) { | |
2865 | global $DB, $USER; | |
2866 | ||
2867 | $lastpageseen = false; | |
2868 | $allattempts = $this->get_attempts($retriescount); | |
2869 | if (!empty($allattempts)) { | |
2870 | $attempt = end($allattempts); | |
2871 | $attemptpage = $this->load_page($attempt->pageid); | |
2872 | $jumpto = $DB->get_field('lesson_answers', 'jumpto', array('id' => $attempt->answerid)); | |
2873 | // Convert the jumpto to a proper page id. | |
2874 | if ($jumpto == 0) { | |
2875 | // Check if a question has been incorrectly answered AND no more attempts at it are left. | |
2876 | $nattempts = $this->get_attempts($attempt->retry, false, $attempt->pageid, $USER->id); | |
2877 | if (count($nattempts) >= $this->properties->maxattempts) { | |
2878 | $lastpageseen = $this->get_next_page($attemptpage->nextpageid); | |
2879 | } else { | |
2880 | $lastpageseen = $attempt->pageid; | |
2881 | } | |
2882 | } else if ($jumpto == LESSON_NEXTPAGE) { | |
2883 | $lastpageseen = $this->get_next_page($attemptpage->nextpageid); | |
2884 | } else if ($jumpto == LESSON_CLUSTERJUMP) { | |
2885 | $lastpageseen = $this->cluster_jump($attempt->pageid); | |
2886 | } else { | |
2887 | $lastpageseen = $jumpto; | |
2888 | } | |
2889 | } | |
2890 | ||
b584c49d | 2891 | if ($branchtables = $this->get_content_pages_viewed($retriescount, $USER->id, 'timeseen DESC')) { |
37029e46 JL |
2892 | // In here, user has viewed a branch table. |
2893 | $lastbranchtable = current($branchtables); | |
2894 | if (count($allattempts) > 0) { | |
2895 | if ($lastbranchtable->timeseen > $attempt->timeseen) { | |
2896 | // This branch table was viewed more recently than the question page. | |
2897 | if (!empty($lastbranchtable->nextpageid)) { | |
2898 | $lastpageseen = $lastbranchtable->nextpageid; | |
2899 | } else { | |
2900 | // Next page ID did not exist prior to MDL-34006. | |
2901 | $lastpageseen = $lastbranchtable->pageid; | |
2902 | } | |
2903 | } | |
2904 | } else { | |
2905 | // Has not answered any questions but has viewed a branch table. | |
2906 | if (!empty($lastbranchtable->nextpageid)) { | |
2907 | $lastpageseen = $lastbranchtable->nextpageid; | |
2908 | } else { | |
2909 | // Next page ID did not exist prior to MDL-34006. | |
2910 | $lastpageseen = $lastbranchtable->pageid; | |
2911 | } | |
2912 | } | |
2913 | } | |
2914 | return $lastpageseen; | |
2915 | } | |
2916 | ||
2917 | /** | |
2918 | * Return the number of retries in a lesson for a given user. | |
2919 | * | |
2920 | * @param int $userid the user id | |
2921 | * @return int the retries count | |
2922 | * @since Moodle 3.3 | |
2923 | */ | |
2924 | public function count_user_retries($userid) { | |
2925 | global $DB; | |
2926 | ||
2927 | return $DB->count_records('lesson_grades', array("lessonid" => $this->properties->id, "userid" => $userid)); | |
2928 | } | |
2929 | ||
2930 | /** | |
2931 | * Check if a user left a timed session. | |
2932 | * | |
2933 | * @param int $retriescount the number of retries for the lesson (the last retry number). | |
2934 | * @return true if the user left the timed session | |
7d7a2a4e | 2935 | * @since Moodle 3.3 |
37029e46 JL |
2936 | */ |
2937 | public function left_during_timed_session($retriescount) { | |
2938 | global $DB, $USER; | |
2939 | ||
2940 | $conditions = array('lessonid' => $this->properties->id, 'userid' => $USER->id, 'retry' => $retriescount); | |
2941 | return $DB->count_records('lesson_attempts', $conditions) > 0 || $DB->count_records('lesson_branch', $conditions) > 0; | |
2942 | } | |
7d7a2a4e JL |
2943 | |
2944 | /** | |
2945 | * Trigger module viewed event and set the module viewed for completion. | |
2946 | * | |
2947 | * @since Moodle 3.3 | |
2948 | */ | |
2949 | public function set_module_viewed() { | |
2950 | global $CFG; | |
2951 | require_once($CFG->libdir . '/completionlib.php'); | |
2952 | ||
2953 | // Trigger module viewed event. | |
2954 | $event = \mod_lesson\event\course_module_viewed::create(array( | |
2955 | 'objectid' => $this->properties->id, | |
2956 | 'context' => $this->get_context() | |
2957 | )); | |
2958 | $event->add_record_snapshot('course_modules', $this->get_cm()); | |
2959 | $event->add_record_snapshot('course', $this->get_courserecord()); | |
2960 | $event->trigger(); | |
2961 | ||
2962 | // Mark as viewed. | |
2963 | $completion = new completion_info($this->get_courserecord()); | |
2964 | $completion->set_module_viewed($this->get_cm()); | |
2965 | } | |
592c94f3 JL |
2966 | |
2967 | /** | |
2968 | * Return the timers in the current lesson for the given user. | |
2969 | * | |
2970 | * @param int $userid the user id | |
2971 | * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter). | |
2972 | * @param string $fields a comma separated list of fields to return | |
2973 | * @param int $limitfrom return a subset of records, starting at this point (optional). | |
2974 | * @param int $limitnum return a subset comprising this many records in total (optional, required if $limitfrom is set). | |
2975 | * @return array list of timers for the given user in the lesson | |
2976 | * @since Moodle 3.3 | |
2977 | */ | |
2978 | public function get_user_timers($userid = null, $sort = '', $fields = '*', $limitfrom = 0, $limitnum = 0) { | |
2979 | global $DB, $USER; | |
2980 | ||
2981 | if ($userid === null) { | |
2982 | $userid = $USER->id; | |
2983 | } | |
2984 | ||
2985 | $params = array('lessonid' => $this->properties->id, 'userid' => $userid); | |
2986 | return $DB->get_records('lesson_timer', $params, $sort, $fields, $limitfrom, $limitnum); | |
2987 | } | |
dbba944e JL |
2988 | |
2989 | /** | |
2990 | * Check if the user is out of time in a timed lesson. | |
2991 | * | |
2992 | * @param stdClass $timer timer object | |
2993 | * @return bool True if the user is on time, false is the user ran out of time | |
2994 | * @since Moodle 3.3 | |
2995 | */ | |
2996 | public function check_time($timer) { | |
2997 | if ($this->properties->timelimit) { | |
2998 | $timeleft = $timer->starttime + $this->properties->timelimit - time(); | |
2999 | if ($timeleft <= 0) { | |
3000 | // Out of time. | |
3001 | $this->add_message(get_string('eolstudentoutoftime', 'lesson')); | |
3002 | return false; | |
3003 | } else if ($timeleft < 60) { | |
3004 | // One minute warning. | |
3005 | $this->add_message(get_string('studentoneminwarning', 'lesson')); | |
3006 | } | |
3007 | } | |
3008 | return true; | |
3009 | } | |
3010 | ||
3011 | /** | |
3012 | * Add different informative messages to the given page. | |
3013 | * | |
3014 | * @param lesson_page $page page object | |
3015 | * @param reviewmode $bool whether we are in review mode or not | |
3016 | * @since Moodle 3.3 | |
3017 | */ | |
3018 | public function add_messages_on_page_view(lesson_page $page, $reviewmode) { | |
3019 | global $DB, $USER; | |
3020 | ||
3021 | if (!$this->can_manage()) { | |
3022 | if ($page->qtype == LESSON_PAGE_BRANCHTABLE && $this->properties->minquestions) { | |
3023 | // Tell student how many questions they have seen, how many are required and their grade. | |
3024 | $ntries = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); | |
3025 | $gradeinfo = lesson_grade($this, $ntries); | |
3026 | if ($gradeinfo->attempts) { | |
3027 | if ($gradeinfo->nquestions < $this->properties->minquestions) { | |
3028 | $a = new stdClass; | |
3029 | $a->nquestions = $gradeinfo->nquestions; | |
3030 | $a->minquestions = $this->properties->minquestions; | |
3031 | $this->add_message(get_string('numberofpagesviewednotice', 'lesson', $a)); | |
3032 | } | |
3033 | ||
3034 | if (!$reviewmode && !$this->properties->retake) { | |
3035 | $this->add_message(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'notify'); | |
3036 | if ($this->properties->grade != GRADE_TYPE_NONE) { | |
3037 | $a = new stdClass; | |
3038 | $a->grade = number_format($gradeinfo->grade * $this->properties->grade / 100, 1); | |
3039 | $a->total = $this->properties->grade; | |
3040 | $this->add_message(get_string('yourcurrentgradeisoutof', 'lesson', $a), 'notify'); | |
3041 | } | |
3042 | } | |
3043 | } | |
3044 | } | |
3045 | } else { | |
3046 | if ($this->properties->timelimit) { | |
3047 | $this->add_message(get_string('teachertimerwarning', 'lesson')); | |
3048 | } | |
3049 | if (lesson_display_teacher_warning($this)) { | |
3050 | // This is the warning msg for teachers to inform them that cluster | |
3051 | // and unseen does not work while logged in as a teacher. | |
3052 | $warningvars = new stdClass(); | |
3053 | $warningvars->cluster = get_string('clusterjump', 'lesson'); | |
3054 | $warningvars->unseen = get_string('unseenpageinbranch', 'lesson'); | |
3055 | $this->add_message(get_string('teacherjumpwarning', 'lesson', $warningvars)); | |
3056 | } | |
3057 | } | |
3058 | } | |
66cd7b8e JL |
3059 | |
3060 | /** | |
3061 | * Get the ongoing score message for the user (depending on the user permission and lesson settings). | |
3062 | * | |
3063 | * @return str the ongoing score message | |
3064 | * @since Moodle 3.3 | |
3065 | */ | |
3066 | public function get_ongoing_score_message() { | |
3067 | global $USER, $DB; | |
3068 | ||
3069 | $context = $this->get_context(); | |
3070 | ||
3071 | if (has_capability('mod/lesson:manage', $context)) { | |
3072 | return get_string('teacherongoingwarning', 'lesson'); | |
3073 | } else { | |
3074 | $ntries = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); | |
3075 | if (isset($USER->modattempts[$this->properties->id])) { | |
3076 | $ntries--; | |
3077 | } | |
3078 | $gradeinfo = lesson_grade($this, $ntries); | |
3079 | $a = new stdClass; | |
3080 | if ($this->properties->custom) { | |
3081 | $a->score = $gradeinfo->earned; | |
3082 | $a->currenthigh = $gradeinfo->total; | |
3083 | return get_string("ongoingcustom", "lesson", $a); | |
3084 | } else { | |
3085 | $a->correct = $gradeinfo->earned; | |
3086 | $a->viewed = $gradeinfo->attempts; | |
3087 | return get_string("ongoingnormal", "lesson", $a); | |
3088 | } | |
3089 | } | |
3090 | } | |
3091 | ||
3092 | /** | |
3093 | * Calculate the progress of the current user in the lesson. | |
3094 | * | |
3095 | * @return int the progress (scale 0-100) | |
3096 | * @since Moodle 3.3 | |
3097 | */ | |
3098 | public function calculate_progress() { | |
3099 | global $USER, $DB; | |
3100 | ||
3101 | // Check if the user is reviewing the attempt. | |
3102 | if (isset($USER->modattempts[$this->properties->id])) { | |
3103 | return 100; | |
3104 | } | |
3105 | ||
3106 | // All of the lesson pages. | |
3107 | $pages = $this->load_all_pages(); | |
3108 | foreach ($pages as $page) { | |
3109 | if ($page->prevpageid == 0) { | |
3110 | $pageid = $page->id; // Find the first page id. | |
3111 | break; | |
3112 | } | |
3113 | } | |
3114 | ||
3115 | // Current attempt number. | |
3116 | if (!$ntries = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id))) { | |
3117 | $ntries = 0; // May not be necessary. | |
3118 | } | |
3119 | ||
3120 | $viewedpageids = array(); | |
3121 | if ($attempts = $this->get_attempts($ntries, false)) { | |
3122 | foreach ($attempts as $attempt) { | |
3123 | $viewedpageids[$attempt->pageid] = $attempt; | |
3124 | } | |
3125 | } | |
3126 | ||
3127 | $viewedbranches = array(); | |
3128 | // Collect all of the branch tables viewed. | |
3129 | if ($branches = $this->get_content_pages_viewed($ntries, $USER->id, 'timeseen ASC', 'id, pageid')) { | |
3130 | foreach ($branches as $branch) { | |
3131 | $viewedbranches[$branch->pageid] = $branch; | |
3132 | } | |
3133 | $viewedpageids = array_merge($viewedpageids, $viewedbranches); | |
3134 | } | |
3135 | ||
3136 | // Filter out the following pages: | |
3137 | // - End of Cluster | |
3138 | // - End of Branch | |
3139 | // - Pages found inside of Clusters | |
3140 | // Do not filter out Cluster Page(s) because we count a cluster as one. | |
3141 | // By keeping the cluster page, we get our 1. | |
3142 | $validpages = array(); | |
3143 | while ($pageid != 0) { | |
3144 | $pageid = $pages[$pageid]->valid_page_and_view($validpages, $viewedpageids); | |
3145 | } | |
3146 | ||
3147 | // Progress calculation as a percent. | |
16ca026c JL |
3148 | $progress = round(count($viewedpageids) / count($validpages), 2) * 100; |
3149 | return (int) $progress; | |
66cd7b8e JL |
3150 | } |
3151 | ||
3152 | /** | |
3153 | * Calculate the correct page and prepare contents for a given page id (could be a page jump id). | |
3154 | * | |
3155 | * @param int $pageid the given page id | |
3156 | * @param mod_lesson_renderer $lessonoutput the lesson output rendered | |
3157 | * @param bool $reviewmode whether we are in review mode or not | |
0259109f | 3158 | * @param bool $redirect Optional, default to true. Set to false to avoid redirection and return the page to redirect. |
66cd7b8e JL |
3159 | * @return array the page object and contents |
3160 | * @throws moodle_exception | |
3161 | * @since Moodle 3.3 | |
3162 | */ | |
0259109f | 3163 | public function prepare_page_and_contents($pageid, $lessonoutput, $reviewmode, $redirect = true) { |
66cd7b8e JL |
3164 | global $USER, $CFG; |
3165 | ||
3166 | $page = $this->load_page($pageid); | |
3167 | // Check if the page is of a special type and if so take any nessecary action. | |
0259109f JL |
3168 | $newpageid = $page->callback_on_view($this->can_manage(), $redirect); |
3169 | ||
3170 | // Avoid redirections returning the jump to special page id. | |
3171 | if (!$redirect && is_numeric($newpageid) && $newpageid < 0) { | |
3172 | return array($newpageid, null, null); | |
3173 | } | |
3174 | ||
66cd7b8e JL |
3175 | if (is_numeric($newpageid)) { |
3176 | $page = $this->load_page($newpageid); | |
3177 | } | |
3178 | ||
3179 | // Add different informative messages to the given page. | |
3180 | $this->add_messages_on_page_view($page, $reviewmode); | |
3181 | ||
3182 | if (is_array($page->answers) && count($page->answers) > 0) { | |
3183 | // This is for modattempts option. Find the users previous answer to this page, | |
3184 | // and then display it below in answer processing. | |
3185 | if (isset($USER->modattempts[$this->properties->id])) { | |
3186 | $retries = $this->count_user_retries($USER->id); | |
3187 | if (!$attempts = $this->get_attempts($retries - 1, false, $page->id)) { | |
3188 | throw new moodle_exception('cannotfindpreattempt', 'lesson'); | |
3189 | } | |
3190 | $attempt = end($attempts); | |
3191 | $USER->modattempts[$this->properties->id] = $attempt; | |
3192 | } else { | |
3193 | $attempt = false; | |
3194 | } | |
3195 | $lessoncontent = $lessonoutput->display_page($this, $page, $attempt); | |
3196 | } else { | |
3197 | require_once($CFG->dirroot . '/mod/lesson/view_form.php'); | |
3198 | $data = new stdClass; | |
3199 | $data->id = $this->get_cm()->id; | |
3200 | $data->pageid = $page->id; | |
3201 | $data->newpageid = $this->get_next_page($page->nextpageid); | |
3202 | ||
3203 | $customdata = array( | |
3204 | 'title' => $page->title, | |
3205 | 'contents' => $page->get_contents() | |
3206 | ); | |
3207 | $mform = new lesson_page_without_answers($CFG->wwwroot.'/mod/lesson/continue.php', $customdata); | |
3208 | $mform->set_data($data); | |
3209 | ob_start(); | |
3210 | $mform->display(); | |
3211 | $lessoncontent = ob_get_contents(); | |
3212 | ob_end_clean(); | |
3213 | } | |
3214 | ||
0259109f | 3215 | return array($page->id, $page, $lessoncontent); |
66cd7b8e | 3216 | } |
61b51764 JL |
3217 | |
3218 | /** | |
707d50d1 | 3219 | * This returns a real page id to jump to (or LESSON_EOL) after processing page responses. |
61b51764 | 3220 | * |
707d50d1 JL |
3221 | * @param lesson_page $page lesson page |
3222 | * @param int $newpageid the new page id | |
3223 | * @return int the real page to jump to (or end of lesson) | |
61b51764 JL |
3224 | * @since Moodle 3.3 |
3225 | */ | |
707d50d1 | 3226 | public function calculate_new_page_on_jump(lesson_page $page, $newpageid) { |
61b51764 JL |
3227 | global $USER, $DB; |
3228 | ||
3229 | $canmanage = $this->can_manage(); | |
61b51764 | 3230 | |
707d50d1 | 3231 | if (isset($USER->modattempts[$this->properties->id])) { |
61b51764 | 3232 | // Make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time. |
e1f88fe7 | 3233 | if ($USER->modattempts[$this->properties->id]->pageid == $page->id && $page->nextpageid == 0) { |
61b51764 | 3234 | // Remember, this session variable holds the pageid of the last page that the user saw. |
707d50d1 | 3235 | $newpageid = LESSON_EOL; |
61b51764 | 3236 | } else { |
e1f88fe7 | 3237 | $nretakes = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); |
61b51764 | 3238 | $nretakes--; // Make sure we are looking at the right try. |
e1f88fe7 | 3239 | $attempts = $DB->get_records("lesson_attempts", array("lessonid" => $this->properties->id, "userid" => $USER->id, "retry" => $nretakes), "timeseen", "id, pageid"); |
61b51764 JL |
3240 | $found = false; |
3241 | $temppageid = 0; | |
3242 | // Make sure that the newpageid always defaults to something valid. | |
707d50d1 | 3243 | $newpageid = LESSON_EOL; |
61b51764 JL |
3244 | foreach ($attempts as $attempt) { |
3245 | if ($found && $temppageid != $attempt->pageid) { | |
3246 | // Now try to find the next page, make sure next few attempts do no belong to current page. | |
707d50d1 | 3247 | $newpageid = $attempt->pageid; |
61b51764 JL |
3248 | break; |
3249 | } | |
3250 | if ($attempt->pageid == $page->id) { | |
3251 | $found = true; // If found current page. | |
3252 | $temppageid = $attempt->pageid; | |
3253 | } | |
3254 | } | |
3255 | } | |
707d50d1 | 3256 | } else if ($newpageid != LESSON_CLUSTERJUMP && $page->id != 0 && $newpageid > 0) { |
61b51764 JL |
3257 | // Going to check to see if the page that the user is going to view next, is a cluster page. |
3258 | // If so, dont display, go into the cluster. | |
707d50d1 JL |
3259 | // The $newpageid > 0 is used to filter out all of the negative code jumps. |
3260 | $newpage = $this->load_page($newpageid); | |
3261 | if ($overridenewpageid = $newpage->override_next_page($newpageid)) { | |
3262 | $newpageid = $overridenewpageid; | |
61b51764 | 3263 | } |
707d50d1 | 3264 | } else if ($newpageid == LESSON_UNSEENBRANCHPAGE) { |
61b51764 JL |
3265 | if ($canmanage) { |
3266 | if ($page->nextpageid == 0) { | |
707d50d1 | 3267 | $newpageid = LESSON_EOL; |
61b51764 | 3268 | } else { |
707d50d1 | 3269 | $newpageid = $page->nextpageid; |
61b51764 JL |
3270 | } |
3271 | } else { | |
707d50d1 | 3272 | $newpageid = lesson_unseen_question_jump($this, $USER->id, $page->id); |
61b51764 | 3273 | } |
707d50d1 JL |
3274 | } else if ($newpageid == LESSON_PREVIOUSPAGE) { |
3275 | $newpageid = $page->prevpageid; | |
3276 | } else if ($newpageid == LESSON_RANDOMPAGE) { | |
3277 | $newpageid = lesson_random_question_jump($this, $page->id); | |
3278 | } else if ($newpageid == LESSON_CLUSTERJUMP) { | |
61b51764 JL |
3279 | if ($canmanage) { |
3280 | if ($page->nextpageid == 0) { // If teacher, go to next page. | |
707d50d1 | 3281 | $newpageid = LESSON_EOL; |
61b51764 | 3282 | } else { |
707d50d1 | 3283 | $newpageid = $page->nextpageid; |
61b51764 JL |
3284 | } |
3285 | } else { | |
707d50d1 | 3286 | $newpageid = $this->cluster_jump($page->id); |
61b51764 | 3287 | } |
707d50d1 JL |
3288 | } else if ($newpageid == 0) { |
3289 | $newpageid = $page->id; | |
3290 | } else if ($newpageid == LESSON_NEXTPAGE) { | |
3291 | $newpageid = $this->get_next_page($page->nextpageid); | |
61b51764 | 3292 | } |
707d50d1 JL |
3293 | |
3294 | return $newpageid; | |
3295 | } | |
3296 | ||
3297 | /** | |
3298 | * Process page responses. | |
3299 | * | |
3300 | * @param lesson_page $page page object | |
3301 | * @since Moodle 3.3 | |
3302 | */ | |
3303 | public function process_page_responses(lesson_page $page) { | |
3304 | $context = $this->get_context(); | |
3305 | ||
3306 | // Check the page has answers [MDL-25632]. | |
3307 | if (count($page->answers) > 0) { | |
3308 | $result = $page->record_attempt($context); | |
3309 | } else { | |
3310 | // The page has no answers so we will just progress to the next page in the | |
3311 | // sequence (as set by newpageid). | |
3312 | $result = new stdClass; | |
3313 | $result->newpageid = optional_param('newpageid', $page->nextpageid, PARAM_INT); | |
3314 | $result->nodefaultresponse = true; | |
e7a11c20 | 3315 | $result->inmediatejump = false; |
707d50d1 JL |
3316 | } |
3317 | ||
3318 | if ($result->inmediatejump) { | |
3319 | return $result; | |
3320 | } | |
3321 | ||
3322 | $result->newpageid = $this->calculate_new_page_on_jump($page, $result->newpageid); | |
3323 | ||
61b51764 JL |
3324 | return $result; |
3325 | } | |
3326 | ||
3327 | /** | |
3328 | * Add different informative messages to the given page. | |
3329 | * | |
3330 | * @param lesson_page $page page object | |
3331 | * @param stdClass $result the page processing result object | |
3332 | * @param bool $reviewmode whether we are in review mode or not | |
3333 | * @since Moodle 3.3 | |
3334 | */ | |
3335 | public function add_messages_on_page_process(lesson_page $page, $result, $reviewmode) { | |
3336 | ||
3337 | if ($this->can_manage()) { | |
3338 | // This is the warning msg for teachers to inform them that cluster and unseen does not work while logged in as a teacher. | |
3339 | if (lesson_display_teacher_warning($this)) { | |
3340 | $warningvars = new stdClass(); | |
3341 | $warningvars->cluster = get_string("clusterjump", "lesson"); | |
3342 | $warningvars->unseen = get_string("unseenpageinbranch", "lesson"); | |
3343 | $this->add_message(get_string("teacherjumpwarning", "lesson", $warningvars)); | |
3344 | } | |
3345 | // Inform teacher that s/he will not see the timer. | |
3346 | if ($this->properties->timelimit) { | |
d96d7295 | 3347 | $this->add_message(get_string("teachertimerwarning", "lesson")); |
61b51764 JL |
3348 | } |
3349 | } | |
3350 | // Report attempts remaining. | |
3351 | if ($result->attemptsremaining != 0 && $this->properties->review && !$reviewmode) { | |
3352 | $this->add_message(get_string('attemptsremaining', 'lesson', $result->attemptsremaining)); | |
3353 | } | |
3354 | } | |
dfcabd3b JL |
3355 | |
3356 | /** | |
3357 | * Process and return all the information for the end of lesson page. | |
3358 | * | |
3359 | * @param string $outoftime used to check to see if the student ran out of time | |
3360 | * @return stdclass an object with all the page data ready for rendering | |
3361 | * @since Moodle 3.3 | |
3362 | */ | |
3363 | public function process_eol_page($outoftime) { | |
3364 | global $DB, $USER; | |
3365 | ||
3366 | $course = $this->get_courserecord(); | |
3367 | $cm = $this->get_cm(); | |
3368 | $canmanage = $this->can_manage(); | |
3369 | ||
3370 | // Init all the possible fields and values. | |
3371 | $data = (object) array( | |
3372 | 'gradelesson' => true, | |
3373 | 'notenoughtimespent' => false, | |
3374 | 'numberofpagesviewed' => false, | |
3375 | 'youshouldview' => false, | |
3376 | 'numberofcorrectanswers' => false, | |
3377 | 'displayscorewithessays' => false, | |
3378 | 'displayscorewithoutessays' => false, | |
3379 | 'yourcurrentgradeisoutof' => false, | |
3380 | 'eolstudentoutoftimenoanswers' => false, | |
3381 | 'welldone' => false, | |
3382 | 'progressbar' => false, | |
3383 | 'displayofgrade' => false, | |
3384 | 'reviewlesson' => false, | |
3385 | 'modattemptsnoteacher' => false, | |
3386 | 'activitylink' => false, | |
f3d9512d | 3387 | 'progresscompleted' => false, |
dfcabd3b JL |
3388 | ); |
3389 | ||
3390 | $ntries = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); | |
3391 | if (isset($USER->modattempts[$this->properties->id])) { | |
3392 | $ntries--; // Need to look at the old attempts :). | |
3393 | } | |
3394 | ||
3395 | $gradeinfo = lesson_grade($this, $ntries); | |
3396 | $data->gradeinfo = $gradeinfo; | |
3397 | if ($this->properties->custom && !$canmanage) { | |
3398 | // Before we calculate the custom score make sure they answered the minimum | |
3399 | // number of questions. We only need to do this for custom scoring as we can | |
3400 | // not get the miniumum score the user should achieve. If we are not using | |
3401 | // custom scoring (so all questions are valued as 1) then we simply check if | |
3402 | // they answered more than the minimum questions, if not, we mark it out of the | |
3403 | // number specified in the minimum questions setting - which is done in lesson_grade(). | |
3404 | // Get the number of answers given. | |
3405 | if ($gradeinfo->nquestions < $this->properties->minquestions) { | |
3406 | $data->gradelesson = false; | |
3407 | $a = new stdClass; | |
3408 | $a->nquestions = $gradeinfo->nquestions; | |
3409 | $a->minquestions = $this->properties->minquestions; | |
3410 | $this->add_message(get_string('numberofpagesviewednotice', 'lesson', $a)); | |
3411 | } | |
3412 | } | |
3413 | ||
3414 | if (!$canmanage) { | |
3415 | if ($data->gradelesson) { | |
3416 | // Store this now before any modifications to pages viewed. | |
3417 | $progresscompleted = $this->calculate_progress(); | |
3418 | ||
3419 | // Update the clock / get time information for this user. | |
3420 | $this->stop_timer(); | |
3421 | ||
3422 | // Update completion state. | |
3423 | $completion = new completion_info($course); | |
3424 | if ($completion->is_enabled($cm) && $this->properties->completionendreached) { | |
3425 | $completion->update_state($cm, COMPLETION_COMPLETE); | |
3426 | } | |
3427 | ||
3428 | if ($this->properties->completiontimespent > 0) { | |
3429 | $duration = $DB->get_field_sql( | |
3430 | "SELECT SUM(lessontime - starttime) | |
3431 | FROM {lesson_timer} | |
3432 | WHERE lessonid = :lessonid | |
3433 | AND userid = :userid", | |
3434 | array('userid' => $USER->id, 'lessonid' => $this->properties->id)); | |
3435 | if (!$duration) { | |
3436 | $duration = 0; | |
3437 | } | |
3438 | ||
3439 | // If student has not spend enough time in the lesson, display a message. | |
3440 | if ($duration < $this->properties->completiontimespent) { | |
3441 | $a = new stdClass; | |
3442 | $a->timespentraw = $duration; | |
3443 | $a->timespent = format_time($duration); | |
3444 | $a->timerequiredraw = $this->properties->completiontimespent; | |
3445 | $a->timerequired = format_time($this->properties->completiontimespent); | |
3446 | $data->notenoughtimespent = $a; | |
3447 | } | |
3448 | } | |
3449 | ||
3450 | if ($gradeinfo->attempts) { | |
3451 | if (!$this->properties->custom) { | |
3452 | $data->numberofpagesviewed = $gradeinfo->nquestions; | |
3453 | if ($this->properties->minquestions) { | |
3454 | if ($gradeinfo->nquestions < $this->properties->minquestions) { | |
3455 | $data->youshouldview = $this->properties->minquestions; | |
3456 | } | |
3457 | } | |
3458 | $data->numberofcorrectanswers = $gradeinfo->earned; | |
3459 | } | |
3460 | $a = new stdClass; | |
3461 | $a->score = $gradeinfo->earned; | |
3462 | $a->grade = $gradeinfo->total; | |
3463 | if ($gradeinfo->nmanual) { | |
3464 | $a->tempmaxgrade = $gradeinfo->total - $gradeinfo->manualpoints; | |
3465 | $a->essayquestions = $gradeinfo->nmanual; | |
3466 | $data->displayscorewithessays = $a; | |
3467 | } else { | |
3468 | $data->displayscorewithoutessays = $a; | |
3469 | } | |
3470 | if ($this->properties->grade != GRADE_TYPE_NONE) { | |
3471 | $a = new stdClass; | |
3472 | $a->grade = number_format($gradeinfo->grade * $this->properties->grade / 100, 1); | |
3473 | $a->total = $this->properties->grade; | |
3474 | $data->yourcurrentgradeisoutof = $a; | |
3475 | } | |
3476 | ||
3477 | $grade = new stdClass(); | |
3478 | $grade->lessonid = $this->properties->id; | |
3479 | $grade->userid = $USER->id; | |
3480 | $grade->grade = $gradeinfo->grade; | |
3481 | $grade->completed = time(); | |
3482 | if (isset($USER->modattempts[$this->properties->id])) { // If reviewing, make sure update old grade record. | |
3483 | if (!$grades = $DB->get_records("lesson_grades", | |
3484 | array("lessonid" => $this->properties->id, "userid" => $USER->id), "completed DESC", '*', 0, 1)) { | |
3485 | throw new moodle_exception('cannotfindgrade', 'lesson'); | |
3486 | } | |
3487 | $oldgrade = array_shift($grades); | |
3488 | $grade->id = $oldgrade->id; | |
3489 | $DB->update_record("lesson_grades", $grade); | |
3490 | } else { | |
3491 | $newgradeid = $DB->insert_record("lesson_grades", $grade); | |
3492 | } | |
3493 | } else { | |
3494 | if ($this->properties->timelimit) { | |
3495 | if ($outoftime == 'normal') { | |
3496 | $grade = new stdClass(); | |
3497 | $grade->lessonid = $this->properties->id; | |
3498 | $grade->userid = $USER->id; | |
3499 | $grade->grade = 0; | |
3500 | $grade->completed = time(); | |
3501 | $newgradeid = $DB->insert_record("lesson_grades", $grade); | |
3502 | $data->eolstudentoutoftimenoanswers = true; | |
3503 | } | |
3504 | } else { | |
3505 | $data->welldone = true; | |
3506 | } | |
3507 | } | |
3508 | ||
3509 | // Update central gradebook. | |
3510 | lesson_update_grades($this, $USER->id); | |
3511 | $data->progresscompleted = $progresscompleted; | |
3512 | } | |
3513 | } else { | |
3514 | // Display for teacher. | |
3515 | if ($this->properties->grade != GRADE_TYPE_NONE) { | |
3516 | $data->displayofgrade = true; | |
3517 | } | |
3518 | } | |
3519 | ||
3520 | if ($this->properties->modattempts && !$canmanage) { | |
3521 | // Make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time | |
3522 | // look at the attempt records to find the first QUESTION page that the user answered, then use that page id | |
3523 | // to pass to view again. This is slick cause it wont call the empty($pageid) code | |
3524 | // $ntries is decremented above. | |
3525 | if (!$attempts = $this->get_attempts($ntries)) { | |
3526 | $attempts = array(); | |
3527 | $url = new moodle_url('/mod/lesson/view.php', array('id' => $cm->id)); | |
3528 | } else { | |
3529 | $firstattempt = current($attempts); | |
3530 | $pageid = $firstattempt->pageid; | |
3531 | // If the student wishes to review, need to know the last question page that the student answered. | |
3532 | // This will help to make sure that the student can leave the lesson via pushing the continue button. | |
3533 | $lastattempt = end($attempts); | |
3534 | $USER->modattempts[$this->properties->id] = $lastattempt->pageid; | |
3535 | ||
3536 | $url = new moodle_url('/mod/lesson/view.php', array('id' => $cm->id, 'pageid' => $pageid)); | |
3537 | } | |
0fc1f24b | 3538 | $data->reviewlesson = $url->out(false); |
dfcabd3b JL |
3539 | } else if ($this->properties->modattempts && $canmanage) { |
3540 | $data->modattemptsnoteacher = true; | |
3541 | } | |
3542 | ||
3543 | if ($this->properties->activitylink) { | |
3544 | $data->activitylink = $this->link_for_activitylink(); | |
3545 | } | |
3546 | return $data; | |
3547 | } | |
1e7f8ea2 PS |
3548 | } |
3549 | ||
3550 | ||
3551 | /** | |
3552 | * Abstract class to provide a core functions to the all lesson classes | |
3553 | * | |
3554 | * This class should be abstracted by ALL classes with the lesson module to ensure | |
3555 | * that all classes within this module can be interacted with in the same way. | |
3556 | * | |
3557 | * This class provides the user with a basic properties array that can be fetched | |
ff85f902 | 3558 | * or set via magic methods, or alternatively by defining methods get_blah() or |
1e7f8ea2 PS |
3559 | * set_blah() within the extending object. |
3560 | * | |
cc3dbaaa PS |
3561 | * @copyright 2009 Sam Hemelryk |
3562 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1e7f8ea2 PS |
3563 | */ |
3564 | abstract class lesson_base { | |
3565 | ||
3566 | /** | |
3567 | * An object containing properties | |
3568 | * @var stdClass | |
3569 | */ | |
3570 | protected $properties; | |
3571 | ||
3572 | /** | |
3573 | * The constructor | |
3574 | * @param stdClass $properties | |
3575 | */ | |
3576 | public function __construct($properties) { | |
3577 | $this->properties = (object)$properties; | |
3578 | } | |
3579 | ||
3580 | /** | |
3581 | * Magic property method | |
3582 | * | |
3583 | * Attempts to call a set_$key method if one exists otherwise falls back | |
3584 | * to simply set the property | |
3585 | * | |
3586 | * @param string $key | |
3587 | * @param mixed $value | |
3588 | */ | |
3589 | public function __set($key, $value) { | |
3590 | if (method_exists($this, 'set_'.$key)) { | |
3591 | $this->{'set_'.$key}($value); | |
3592 | } | |
3593 | $this->properties->{$key} = $value; | |
3594 | } | |
3595 | ||
3596 | /** | |
3597 | * Magic get method | |
3598 | * | |
3599 | * Attempts to call a get_$key method to return the property and ralls over | |
3600 | * to return the raw property | |
3601 | * | |
3602 | * @param str $key | |
3603 | * @return mixed | |
3604 | */ | |
3605 | public function __get($key) { | |
3606 | if (method_exists($this, 'get_'.$key)) { | |
3607 | return $this->{'get_'.$key}(); | |
3608 | } | |
3609 | return $this->properties->{$key}; | |
3610 | } | |
3611 | ||
3612 | /** | |
3613 | * Stupid PHP needs an isset magic method if you use the get magic method and | |
3614 | * still want empty calls to work.... blah ~! | |
3615 | * | |
3616 | * @param string $key | |
3617 | * @return bool | |
3618 | */ | |
3619 | public function __isset($key) { | |
3620 | if (method_exists($this, 'get_'.$key)) { | |
3621 | $val = $this->{'get_'.$key}(); | |
3622 | return !empty($val); | |
3623 | } | |
3624 | return !empty($this->properties->{$key}); | |
3625 | } | |