Commit | Line | Data |
---|---|---|
8cc86111 | 1 | <?php |
2 | ||
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 | * Standard library of functions and constants for lesson | |
20 | * | |
9b24f68b | 21 | * @package mod_lesson |
8d1a3963 | 22 | * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} |
cc3dbaaa | 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
5491947a | 24 | **/ |
97c44107 | 25 | |
28f5bf67 PS |
26 | defined('MOODLE_INTERNAL') || die(); |
27 | ||
1e7f8ea2 | 28 | /* Do not include any libraries here! */ |
97c44107 | 29 | |
acf85537 | 30 | /** |
31 | * Given an object containing all the necessary data, | |
32 | * (defined by the form in mod_form.php) this function | |
33 | * will create a new instance and return the id number | |
34 | * of the new instance. | |
35 | * | |
8cc86111 | 36 | * @global object |
37 | * @global object | |
acf85537 | 38 | * @param object $lesson Lesson post data from the form |
39 | * @return int | |
40 | **/ | |
0a4abb73 | 41 | function lesson_add_instance($data, $mform) { |
1e7f8ea2 | 42 | global $DB; |
bbcbc0fe | 43 | |
0a4abb73 | 44 | $cmid = $data->coursemodule; |
684c3be7 MN |
45 | $draftitemid = $data->mediafile; |
46 | $context = context_module::instance($cmid); | |
5f649aaa | 47 | |
0a4abb73 | 48 | lesson_process_pre_save($data); |
271fea97 | 49 | |
0a4abb73 SH |
50 | unset($data->mediafile); |
51 | $lessonid = $DB->insert_record("lesson", $data); | |
52 | $data->id = $lessonid; | |
97c44107 | 53 | |
684c3be7 | 54 | lesson_update_media_file($lessonid, $context, $draftitemid); |
0a4abb73 SH |
55 | |
56 | lesson_process_post_save($data); | |
57 | ||
58 | lesson_grade_item_update($data); | |
92bcca38 | 59 | |
b5914b61 | 60 | return $lessonid; |
bbcbc0fe | 61 | } |
62 | ||
acf85537 | 63 | /** |
64 | * Given an object containing all the necessary data, | |
65 | * (defined by the form in mod_form.php) this function | |
66 | * will update an existing instance with new data. | |
67 | * | |
8cc86111 | 68 | * @global object |
acf85537 | 69 | * @param object $lesson Lesson post data from the form |
70 | * @return boolean | |
71 | **/ | |
0a4abb73 | 72 | function lesson_update_instance($data, $mform) { |
c18269c7 | 73 | global $DB; |
bbcbc0fe | 74 | |
0a4abb73 SH |
75 | $data->id = $data->instance; |
76 | $cmid = $data->coursemodule; | |
684c3be7 MN |
77 | $draftitemid = $data->mediafile; |
78 | $context = context_module::instance($cmid); | |
1535c81b | 79 | |
0a4abb73 | 80 | lesson_process_pre_save($data); |
97c44107 | 81 | |
0a4abb73 | 82 | unset($data->mediafile); |
1e7f8ea2 | 83 | $DB->update_record("lesson", $data); |
97c44107 | 84 | |
684c3be7 | 85 | lesson_update_media_file($data->id, $context, $draftitemid); |
0a4abb73 SH |
86 | |
87 | lesson_process_post_save($data); | |
92bcca38 | 88 | |
89 | // update grade item definition | |
0a4abb73 | 90 | lesson_grade_item_update($data); |
92bcca38 | 91 | |
92 | // update grades - TODO: do it only when grading style changes | |
0a4abb73 | 93 | lesson_update_grades($data, 0, false); |
92bcca38 | 94 | |
1e7f8ea2 | 95 | return true; |
bbcbc0fe | 96 | } |
97 | ||
e0e1a83e JMV |
98 | /** |
99 | * This function updates the events associated to the lesson. | |
100 | * If $override is non-zero, then it updates only the events | |
101 | * associated with the specified override. | |
102 | * | |
103 | * @uses LESSON_MAX_EVENT_LENGTH | |
104 | * @param object $lesson the lesson object. | |
105 | * @param object $override (optional) limit to a specific override | |
106 | */ | |
107 | function lesson_update_events($lesson, $override = null) { | |
108 | global $CFG, $DB; | |
109 | ||
9ff8cdd9 | 110 | require_once($CFG->dirroot . '/mod/lesson/locallib.php'); |
e0e1a83e JMV |
111 | require_once($CFG->dirroot . '/calendar/lib.php'); |
112 | ||
113 | // Load the old events relating to this lesson. | |
114 | $conds = array('modulename' => 'lesson', | |
115 | 'instance' => $lesson->id); | |
116 | if (!empty($override)) { | |
117 | // Only load events for this override. | |
118 | if (isset($override->userid)) { | |
119 | $conds['userid'] = $override->userid; | |
120 | } else { | |
121 | $conds['groupid'] = $override->groupid; | |
122 | } | |
123 | } | |
124 | $oldevents = $DB->get_records('event', $conds); | |
125 | ||
126 | // Now make a todo list of all that needs to be updated. | |
127 | if (empty($override)) { | |
128 | // We are updating the primary settings for the lesson, so we | |
129 | // need to add all the overrides. | |
130 | $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id)); | |
131 | // As well as the original lesson (empty override). | |
132 | $overrides[] = new stdClass(); | |
133 | } else { | |
134 | // Just do the one override. | |
135 | $overrides = array($override); | |
136 | } | |
137 | ||
138 | foreach ($overrides as $current) { | |
139 | $groupid = isset($current->groupid) ? $current->groupid : 0; | |
140 | $userid = isset($current->userid) ? $current->userid : 0; | |
141 | $available = isset($current->available) ? $current->available : $lesson->available; | |
142 | $deadline = isset($current->deadline) ? $current->deadline : $lesson->deadline; | |
143 | ||
144 | // Only add open/close events for an override if they differ from the lesson default. | |
145 | $addopen = empty($current->id) || !empty($current->available); | |
146 | $addclose = empty($current->id) || !empty($current->deadline); | |
147 | ||
148 | if (!empty($lesson->coursemodule)) { | |
149 | $cmid = $lesson->coursemodule; | |
150 | } else { | |
151 | $cmid = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)->id; | |
152 | } | |
153 | ||
154 | $event = new stdClass(); | |
155 | $event->description = format_module_intro('lesson', $lesson, $cmid); | |
156 | // Events module won't show user events when the courseid is nonzero. | |
157 | $event->courseid = ($userid) ? 0 : $lesson->course; | |
158 | $event->groupid = $groupid; | |
159 | $event->userid = $userid; | |
160 | $event->modulename = 'lesson'; | |
161 | $event->instance = $lesson->id; | |
162 | $event->timestart = $available; | |
163 | $event->timeduration = max($deadline - $available, 0); | |
164 | $event->visible = instance_is_visible('lesson', $lesson); | |
165 | $event->eventtype = 'open'; | |
166 | ||
167 | // Determine the event name. | |
168 | if ($groupid) { | |
169 | $params = new stdClass(); | |
170 | $params->lesson = $lesson->name; | |
171 | $params->group = groups_get_group_name($groupid); | |
172 | if ($params->group === false) { | |
173 | // Group doesn't exist, just skip it. | |
174 | continue; | |
175 | } | |
176 | $eventname = get_string('overridegroupeventname', 'lesson', $params); | |
177 | } else if ($userid) { | |
178 | $params = new stdClass(); | |
179 | $params->lesson = $lesson->name; | |
180 | $eventname = get_string('overrideusereventname', 'lesson', $params); | |
181 | } else { | |
182 | $eventname = $lesson->name; | |
183 | } | |
184 | if ($addopen or $addclose) { | |
185 | if ($deadline and $available and $event->timeduration <= LESSON_MAX_EVENT_LENGTH) { | |
186 | // Single event for the whole lesson. | |
187 | if ($oldevent = array_shift($oldevents)) { | |
188 | $event->id = $oldevent->id; | |
189 | } else { | |
190 | unset($event->id); | |
191 | } | |
192 | $event->name = $eventname; | |
193 | // The method calendar_event::create will reuse a db record if the id field is set. | |
194 | calendar_event::create($event); | |
195 | } else { | |
196 | // Separate start and end events. | |
197 | $event->timeduration = 0; | |
198 | if ($available && $addopen) { | |
199 | if ($oldevent = array_shift($oldevents)) { | |
200 | $event->id = $oldevent->id; | |
201 | } else { | |
202 | unset($event->id); | |
203 | } | |
204 | $event->name = $eventname.' ('.get_string('lessonopens', 'lesson').')'; | |
205 | // The method calendar_event::create will reuse a db record if the id field is set. | |
206 | calendar_event::create($event); | |
207 | } | |
208 | if ($deadline && $addclose) { | |
209 | if ($oldevent = array_shift($oldevents)) { | |
210 | $event->id = $oldevent->id; | |
211 | } else { | |
212 | unset($event->id); | |
213 | } | |
214 | $event->name = $eventname.' ('.get_string('lessoncloses', 'lesson').')'; | |
215 | $event->timestart = $deadline; | |
216 | $event->eventtype = 'close'; | |
217 | calendar_event::create($event); | |
218 | } | |
219 | } | |
220 | } | |
221 | } | |
222 | ||
223 | // Delete any leftover events. | |
224 | foreach ($oldevents as $badevent) { | |
225 | $badevent = calendar_event::load($badevent); | |
226 | $badevent->delete(); | |
227 | } | |
228 | } | |
229 | ||
efe24976 JP |
230 | /** |
231 | * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson. | |
232 | * | |
233 | * @param int $lessonid The quiz ID. | |
234 | * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides. | |
235 | */ | |
236 | function lesson_get_group_override_priorities($lessonid) { | |
237 | global $DB; | |
238 | ||
239 | // Fetch group overrides. | |
240 | $where = 'lessonid = :lessonid AND groupid IS NOT NULL'; | |
241 | $params = ['lessonid' => $lessonid]; | |
242 | $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline'); | |
243 | if (!$overrides) { | |
244 | return null; | |
245 | } | |
246 | ||
247 | $grouptimeopen = []; | |
248 | $grouptimeclose = []; | |
249 | foreach ($overrides as $override) { | |
250 | if ($override->available !== null && !in_array($override->available, $grouptimeopen)) { | |
251 | $grouptimeopen[] = $override->available; | |
252 | } | |
253 | if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) { | |
254 | $grouptimeclose[] = $override->deadline; | |
255 | } | |
256 | } | |
257 | ||
258 | // Sort open times in descending manner. The earlier open time gets higher priority. | |
259 | rsort($grouptimeopen); | |
260 | // Set priorities. | |
261 | $opengrouppriorities = []; | |
262 | $openpriority = 1; | |
263 | foreach ($grouptimeopen as $timeopen) { | |
264 | $opengrouppriorities[$timeopen] = $openpriority++; | |
265 | } | |
266 | ||
267 | // Sort close times in ascending manner. The later close time gets higher priority. | |
268 | sort($grouptimeclose); | |
269 | // Set priorities. | |
270 | $closegrouppriorities = []; | |
271 | $closepriority = 1; | |
272 | foreach ($grouptimeclose as $timeclose) { | |
273 | $closegrouppriorities[$timeclose] = $closepriority++; | |
274 | } | |
275 | ||
276 | return [ | |
277 | 'open' => $opengrouppriorities, | |
278 | 'close' => $closegrouppriorities | |
279 | ]; | |
280 | } | |
281 | ||
e0e1a83e JMV |
282 | /** |
283 | * This standard function will check all instances of this module | |
284 | * and make sure there are up-to-date events created for each of them. | |
285 | * If courseid = 0, then every lesson event in the site is checked, else | |
286 | * only lesson events belonging to the course specified are checked. | |
287 | * This function is used, in its new format, by restore_refresh_events() | |
288 | * | |
289 | * @param int $courseid | |
290 | * @return bool | |
291 | */ | |
292 | function lesson_refresh_events($courseid = 0) { | |
293 | global $DB; | |
294 | ||
295 | if ($courseid == 0) { | |
296 | if (!$lessons = $DB->get_records('lessons')) { | |
297 | return true; | |
298 | } | |
299 | } else { | |
300 | if (!$lessons = $DB->get_records('lesson', array('course' => $courseid))) { | |
301 | return true; | |
302 | } | |
303 | } | |
304 | ||
305 | foreach ($lessons as $lesson) { | |
306 | lesson_update_events($lesson); | |
307 | } | |
308 | ||
309 | return true; | |
310 | } | |
bbcbc0fe | 311 | |
8cc86111 | 312 | /** |
313 | * Given an ID of an instance of this module, | |
314 | * this function will permanently delete the instance | |
315 | * and any data that depends on it. | |
316 | * | |
317 | * @global object | |
318 | * @param int $id | |
319 | * @return bool | |
320 | */ | |
bbcbc0fe | 321 | function lesson_delete_instance($id) { |
3d53d99e PS |
322 | global $DB, $CFG; |
323 | require_once($CFG->dirroot . '/mod/lesson/locallib.php'); | |
324 | ||
0a4abb73 SH |
325 | $lesson = $DB->get_record("lesson", array("id"=>$id), '*', MUST_EXIST); |
326 | $lesson = new lesson($lesson); | |
327 | return $lesson->delete(); | |
bbcbc0fe | 328 | } |
329 | ||
8cc86111 | 330 | /** |
331 | * Return a small object with summary information about what a | |
332 | * user has done with a given particular instance of this module | |
333 | * Used for user activity reports. | |
334 | * $return->time = the time they did it | |
335 | * $return->info = a short text description | |
336 | * | |
337 | * @global object | |
338 | * @param object $course | |
339 | * @param object $user | |
340 | * @param object $mod | |
341 | * @param object $lesson | |
342 | * @return object | |
343 | */ | |
bbcbc0fe | 344 | function lesson_user_outline($course, $user, $mod, $lesson) { |
fbf85b8d | 345 | global $CFG, $DB; |
df0442c6 | 346 | |
1a96363a NC |
347 | require_once("$CFG->libdir/gradelib.php"); |
348 | $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id); | |
39790bd8 | 349 | $return = new stdClass(); |
fbf85b8d | 350 | |
1a96363a | 351 | if (empty($grades->items[0]->grades)) { |
fbf85b8d | 352 | $return->info = get_string("nolessonattempts", "lesson"); |
bbcbc0fe | 353 | } else { |
1a96363a | 354 | $grade = reset($grades->items[0]->grades); |
fbf85b8d SB |
355 | if (empty($grade->grade)) { |
356 | ||
357 | // Check to see if it an ungraded / incomplete attempt. | |
358 | $sql = "SELECT * | |
359 | FROM {lesson_timer} | |
360 | WHERE lessonid = :lessonid | |
361 | AND userid = :userid | |
362 | ORDER BY starttime DESC"; | |
363 | $params = array('lessonid' => $lesson->id, 'userid' => $user->id); | |
364 | ||
365 | if ($attempts = $DB->get_records_sql($sql, $params, 0, 1)) { | |
366 | $attempt = reset($attempts); | |
367 | if ($attempt->completed) { | |
368 | $return->info = get_string("completed", "lesson"); | |
369 | } else { | |
370 | $return->info = get_string("notyetcompleted", "lesson"); | |
371 | } | |
372 | $return->time = $attempt->lessontime; | |
373 | } else { | |
374 | $return->info = get_string("nolessonattempts", "lesson"); | |
375 | } | |
4433f871 | 376 | } else { |
fbf85b8d SB |
377 | $return->info = get_string("grade") . ': ' . $grade->str_long_grade; |
378 | ||
379 | // Datesubmitted == time created. dategraded == time modified or time overridden. | |
380 | // If grade was last modified by the user themselves use date graded. Otherwise use date submitted. | |
381 | // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704. | |
382 | if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) { | |
383 | $return->time = $grade->dategraded; | |
384 | } else { | |
385 | $return->time = $grade->datesubmitted; | |
386 | } | |
4433f871 | 387 | } |
bbcbc0fe | 388 | } |
389 | return $return; | |
390 | } | |
391 | ||
8cc86111 | 392 | /** |
393 | * Print a detailed representation of what a user has done with | |
394 | * a given particular instance of this module, for user activity reports. | |
395 | * | |
396 | * @global object | |
397 | * @param object $course | |
398 | * @param object $user | |
399 | * @param object $mod | |
400 | * @param object $lesson | |
401 | * @return bool | |
402 | */ | |
bbcbc0fe | 403 | function lesson_user_complete($course, $user, $mod, $lesson) { |
1a96363a NC |
404 | global $DB, $OUTPUT, $CFG; |
405 | ||
406 | require_once("$CFG->libdir/gradelib.php"); | |
407 | ||
408 | $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id); | |
764c4fbc SB |
409 | |
410 | // Display the grade and feedback. | |
411 | if (empty($grades->items[0]->grades)) { | |
412 | echo $OUTPUT->container(get_string("nolessonattempts", "lesson")); | |
413 | } else { | |
1a96363a | 414 | $grade = reset($grades->items[0]->grades); |
764c4fbc SB |
415 | if (empty($grade->grade)) { |
416 | // Check to see if it an ungraded / incomplete attempt. | |
417 | $sql = "SELECT * | |
418 | FROM {lesson_timer} | |
419 | WHERE lessonid = :lessonid | |
420 | AND userid = :userid | |
421 | ORDER by starttime desc"; | |
422 | $params = array('lessonid' => $lesson->id, 'userid' => $user->id); | |
423 | ||
424 | if ($attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) { | |
425 | if ($attempt->completed) { | |
426 | $status = get_string("completed", "lesson"); | |
427 | } else { | |
428 | $status = get_string("notyetcompleted", "lesson"); | |
429 | } | |
430 | } else { | |
431 | $status = get_string("nolessonattempts", "lesson"); | |
432 | } | |
433 | } else { | |
434 | $status = get_string("grade") . ': ' . $grade->str_long_grade; | |
435 | } | |
436 | ||
437 | // Display the grade or lesson status if there isn't one. | |
438 | echo $OUTPUT->container($status); | |
439 | ||
1a96363a NC |
440 | if ($grade->str_feedback) { |
441 | echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback); | |
442 | } | |
443 | } | |
fe75e76b | 444 | |
764c4fbc SB |
445 | // Display the lesson progress. |
446 | // Attempt, pages viewed, questions answered, correct answers, time. | |
646fc290 | 447 | $params = array ("lessonid" => $lesson->id, "userid" => $user->id); |
764c4fbc SB |
448 | $attempts = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen"); |
449 | $branches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen"); | |
450 | if (!empty($attempts) or !empty($branches)) { | |
d68ccdba | 451 | echo $OUTPUT->box_start(); |
3ab269ed | 452 | $table = new html_table(); |
764c4fbc SB |
453 | // Table Headings. |
454 | $table->head = array (get_string("attemptheader", "lesson"), | |
455 | get_string("totalpagesviewedheader", "lesson"), | |
456 | get_string("numberofpagesviewedheader", "lesson"), | |
457 | get_string("numberofcorrectanswersheader", "lesson"), | |
458 | get_string("time")); | |
ac8e16be | 459 | $table->width = "100%"; |
764c4fbc SB |
460 | $table->align = array ("center", "center", "center", "center", "center"); |
461 | $table->size = array ("*", "*", "*", "*", "*"); | |
ac8e16be | 462 | $table->cellpadding = 2; |
463 | $table->cellspacing = 0; | |
bbcbc0fe | 464 | |
465 | $retry = 0; | |
764c4fbc | 466 | $nquestions = 0; |
bbcbc0fe | 467 | $npages = 0; |
468 | $ncorrect = 0; | |
5f649aaa | 469 | |
764c4fbc | 470 | // Filter question pages (from lesson_attempts). |
ac8e16be | 471 | foreach ($attempts as $attempt) { |
472 | if ($attempt->retry == $retry) { | |
473 | $npages++; | |
764c4fbc | 474 | $nquestions++; |
bbcbc0fe | 475 | if ($attempt->correct) { |
476 | $ncorrect++; | |
477 | } | |
478 | $timeseen = $attempt->timeseen; | |
479 | } else { | |
764c4fbc | 480 | $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen)); |
bbcbc0fe | 481 | $retry++; |
764c4fbc | 482 | $nquestions = 1; |
bbcbc0fe | 483 | $npages = 1; |
484 | if ($attempt->correct) { | |
485 | $ncorrect = 1; | |
486 | } else { | |
487 | $ncorrect = 0; | |
488 | } | |
ac8e16be | 489 | } |
490 | } | |
764c4fbc SB |
491 | |
492 | // Filter content pages (from lesson_branch). | |
493 | foreach ($branches as $branch) { | |
494 | if ($branch->retry == $retry) { | |
495 | $npages++; | |
496 | ||
497 | $timeseen = $branch->timeseen; | |
498 | } else { | |
499 | $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen)); | |
500 | $retry++; | |
501 | $npages = 1; | |
502 | } | |
503 | } | |
504 | if ($npages > 0) { | |
505 | $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen)); | |
bbcbc0fe | 506 | } |
16be8974 | 507 | echo html_writer::table($table); |
d68ccdba | 508 | echo $OUTPUT->box_end(); |
bbcbc0fe | 509 | } |
510 | ||
bbcbc0fe | 511 | return true; |
512 | } | |
513 | ||
4342dc32 | 514 | /** |
515 | * Prints lesson summaries on MyMoodle Page | |
516 | * | |
517 | * Prints lesson name, due date and attempt information on | |
518 | * lessons that have a deadline that has not already passed | |
519 | * and it is available for taking. | |
520 | * | |
8cc86111 | 521 | * @global object |
522 | * @global stdClass | |
523 | * @global object | |
524 | * @uses CONTEXT_MODULE | |
4342dc32 | 525 | * @param array $courses An array of course objects to get lesson instances from |
526 | * @param array $htmlarray Store overview output array( course ID => 'lesson' => HTML output ) | |
8cc86111 | 527 | * @return void |
4342dc32 | 528 | */ |
529 | function lesson_print_overview($courses, &$htmlarray) { | |
fe75e76b | 530 | global $USER, $CFG, $DB, $OUTPUT; |
4342dc32 | 531 | |
532 | if (!$lessons = get_all_instances_in_courses('lesson', $courses)) { | |
533 | return; | |
534 | } | |
535 | ||
673e0d5a SB |
536 | // Get all of the current users attempts on all lessons. |
537 | $params = array($USER->id); | |
538 | $sql = 'SELECT lessonid, userid, count(userid) as attempts | |
539 | FROM {lesson_grades} | |
540 | WHERE userid = ? | |
541 | GROUP BY lessonid, userid'; | |
542 | $allattempts = $DB->get_records_sql($sql, $params); | |
543 | $completedattempts = array(); | |
544 | foreach ($allattempts as $myattempt) { | |
545 | $completedattempts[$myattempt->lessonid] = $myattempt->attempts; | |
546 | } | |
547 | ||
548 | // Get the current course ID. | |
549 | $listoflessons = array(); | |
550 | foreach ($lessons as $lesson) { | |
551 | $listoflessons[] = $lesson->id; | |
552 | } | |
553 | // Get the last page viewed by the current user for every lesson in this course. | |
554 | list($insql, $inparams) = $DB->get_in_or_equal($listoflessons, SQL_PARAMS_NAMED); | |
2c4b43d9 MN |
555 | $dbparams = array_merge($inparams, array('userid' => $USER->id)); |
556 | ||
557 | // Get the lesson attempts for the user that have the maximum 'timeseen' value. | |
558 | $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.answerid as nextpageid, p.qtype "; | |
559 | $from = "FROM {lesson_attempts} l | |
560 | JOIN ( | |
561 | SELECT idselect.lessonid, idselect.userid, MAX(idselect.id) AS id | |
562 | FROM {lesson_attempts} idselect | |
563 | JOIN ( | |
564 | SELECT lessonid, userid, MAX(timeseen) AS timeseen | |
565 | FROM {lesson_attempts} | |
566 | WHERE userid = :userid | |
567 | AND lessonid $insql | |
568 | GROUP BY userid, lessonid | |
569 | ) timeselect | |
570 | ON timeselect.timeseen = idselect.timeseen | |
571 | AND timeselect.userid = idselect.userid | |
572 | AND timeselect.lessonid = idselect.lessonid | |
573 | GROUP BY idselect.userid, idselect.lessonid | |
574 | ) aid | |
575 | ON l.id = aid.id | |
576 | JOIN {lesson_pages} p | |
577 | ON l.pageid = p.id "; | |
578 | $lastattempts = $DB->get_records_sql($select . $from, $dbparams); | |
579 | ||
580 | // Now, get the lesson branches for the user that have the maximum 'timeseen' value. | |
581 | $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.nextpageid, p.qtype "; | |
582 | $from = str_replace('{lesson_attempts}', '{lesson_branch}', $from); | |
583 | $lastbranches = $DB->get_records_sql($select . $from, $dbparams); | |
673e0d5a SB |
584 | |
585 | $lastviewed = array(); | |
586 | foreach ($lastattempts as $lastattempt) { | |
2c4b43d9 MN |
587 | $lastviewed[$lastattempt->lessonid] = $lastattempt; |
588 | } | |
589 | ||
590 | // Go through the branch times and record the 'timeseen' value if it doesn't exist | |
591 | // for the lesson, or replace it if it exceeds the current recorded time. | |
592 | foreach ($lastbranches as $lastbranch) { | |
593 | if (!isset($lastviewed[$lastbranch->lessonid])) { | |
594 | $lastviewed[$lastbranch->lessonid] = $lastbranch; | |
595 | } else if ($lastviewed[$lastbranch->lessonid]->timeseen < $lastbranch->timeseen) { | |
596 | $lastviewed[$lastbranch->lessonid] = $lastbranch; | |
673e0d5a SB |
597 | } |
598 | } | |
599 | ||
600 | // Since we have lessons in this course, now include the constants we need. | |
601 | require_once($CFG->dirroot . '/mod/lesson/locallib.php'); | |
4342dc32 | 602 | |
603 | $now = time(); | |
604 | foreach ($lessons as $lesson) { | |
605 | if ($lesson->deadline != 0 // The lesson has a deadline | |
606 | and $lesson->deadline >= $now // And it is before the deadline has been met | |
607 | and ($lesson->available == 0 or $lesson->available <= $now)) { // And the lesson is available | |
608 | ||
673e0d5a SB |
609 | // Visibility. |
610 | $class = (!$lesson->visible) ? 'dimmed' : ''; | |
4342dc32 | 611 | |
673e0d5a SB |
612 | // Context. |
613 | $context = context_module::instance($lesson->coursemodule); | |
4342dc32 | 614 | |
673e0d5a SB |
615 | // Link to activity. |
616 | $url = new moodle_url('/mod/lesson/view.php', array('id' => $lesson->coursemodule)); | |
617 | $url = html_writer::link($url, format_string($lesson->name, true, array('context' => $context)), array('class' => $class)); | |
618 | $str = $OUTPUT->box(get_string('lessonname', 'lesson', $url), 'name'); | |
619 | ||
620 | // Deadline. | |
fe75e76b | 621 | $str .= $OUTPUT->box(get_string('lessoncloseson', 'lesson', userdate($lesson->deadline)), 'info'); |
4342dc32 | 622 | |
673e0d5a SB |
623 | // Attempt information. |
624 | if (has_capability('mod/lesson:manage', $context)) { | |
625 | // This is a teacher, Get the Number of user attempts. | |
c1500a82 | 626 | $attempts = $DB->count_records('lesson_grades', array('lessonid' => $lesson->id)); |
fe75e76b | 627 | $str .= $OUTPUT->box(get_string('xattempts', 'lesson', $attempts), 'info'); |
673e0d5a | 628 | $str = $OUTPUT->box($str, 'lesson overview'); |
4342dc32 | 629 | } else { |
673e0d5a SB |
630 | // This is a student, See if the user has at least started the lesson. |
631 | if (isset($lastviewed[$lesson->id]->timeseen)) { | |
632 | // See if the user has finished this attempt. | |
633 | if (isset($completedattempts[$lesson->id]) && | |
634 | ($completedattempts[$lesson->id] == ($lastviewed[$lesson->id]->retry + 1))) { | |
635 | // Are additional attempts allowed? | |
636 | if ($lesson->retake) { | |
637 | // User can retake the lesson. | |
638 | $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info'); | |
639 | $str = $OUTPUT->box($str, 'lesson overview'); | |
640 | } else { | |
641 | // User has completed the lesson and no retakes are allowed. | |
642 | $str = ''; | |
643 | } | |
644 | ||
645 | } else { | |
646 | // The last attempt was not finished or the lesson does not contain questions. | |
647 | // See if the last page viewed was a branchtable. | |
648 | require_once($CFG->dirroot . '/mod/lesson/pagetypes/branchtable.php'); | |
649 | if ($lastviewed[$lesson->id]->qtype == LESSON_PAGE_BRANCHTABLE) { | |
650 | // See if the next pageid is the end of lesson. | |
651 | if ($lastviewed[$lesson->id]->nextpageid == LESSON_EOL) { | |
652 | // The last page viewed was the End of Lesson. | |
653 | if ($lesson->retake) { | |
654 | // User can retake the lesson. | |
655 | $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info'); | |
656 | $str = $OUTPUT->box($str, 'lesson overview'); | |
657 | } else { | |
658 | // User has completed the lesson and no retakes are allowed. | |
659 | $str = ''; | |
660 | } | |
661 | ||
662 | } else { | |
663 | // The last page viewed was NOT the end of lesson. | |
664 | $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info'); | |
665 | $str = $OUTPUT->box($str, 'lesson overview'); | |
666 | } | |
667 | ||
668 | } else { | |
669 | // Last page was a question page, so the attempt is not completed yet. | |
670 | $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info'); | |
671 | $str = $OUTPUT->box($str, 'lesson overview'); | |
672 | } | |
673 | } | |
674 | ||
4342dc32 | 675 | } else { |
673e0d5a SB |
676 | // User has not yet started this lesson. |
677 | $str .= $OUTPUT->box(get_string('nolessonattempts', 'lesson'), 'info'); | |
678 | $str = $OUTPUT->box($str, 'lesson overview'); | |
4342dc32 | 679 | } |
680 | } | |
673e0d5a SB |
681 | if (!empty($str)) { |
682 | if (empty($htmlarray[$lesson->course]['lesson'])) { | |
683 | $htmlarray[$lesson->course]['lesson'] = $str; | |
684 | } else { | |
685 | $htmlarray[$lesson->course]['lesson'] .= $str; | |
686 | } | |
4342dc32 | 687 | } |
688 | } | |
689 | } | |
690 | } | |
691 | ||
8cc86111 | 692 | /** |
693 | * Function to be run periodically according to the moodle cron | |
694 | * This function searches for things that need to be done, such | |
695 | * as sending out mail, toggling flags etc ... | |
696 | * @global stdClass | |
697 | * @return bool true | |
698 | */ | |
bbcbc0fe | 699 | function lesson_cron () { |
bbcbc0fe | 700 | global $CFG; |
701 | ||
702 | return true; | |
703 | } | |
704 | ||
92bcca38 | 705 | /** |
706 | * Return grade for given user or all users. | |
707 | * | |
8cc86111 | 708 | * @global stdClass |
709 | * @global object | |
92bcca38 | 710 | * @param int $lessonid id of lesson |
711 | * @param int $userid optional user id, 0 means all users | |
712 | * @return array array of grades, false if none | |
713 | */ | |
714 | function lesson_get_user_grades($lesson, $userid=0) { | |
646fc290 | 715 | global $CFG, $DB; |
bbcbc0fe | 716 | |
b920f6e8 | 717 | $params = array("lessonid" => $lesson->id,"lessonid2" => $lesson->id); |
fe75e76b | 718 | |
85c13c69 | 719 | if (!empty($userid)) { |
646fc290 | 720 | $params["userid"] = $userid; |
b920f6e8 | 721 | $params["userid2"] = $userid; |
646fc290 | 722 | $user = "AND u.id = :userid"; |
b920f6e8 | 723 | $fuser = "AND uu.id = :userid2"; |
646fc290 | 724 | } |
725 | else { | |
e57bc529 | 726 | $user=""; |
646fc290 | 727 | $fuser=""; |
728 | } | |
fe75e76b | 729 | |
512ce792 | 730 | if ($lesson->retake) { |
731 | if ($lesson->usemaxgrade) { | |
ac9b0805 | 732 | $sql = "SELECT u.id, u.id AS userid, MAX(g.grade) AS rawgrade |
646fc290 | 733 | FROM {user} u, {lesson_grades} g |
734 | WHERE u.id = g.userid AND g.lessonid = :lessonid | |
92bcca38 | 735 | $user |
736 | GROUP BY u.id"; | |
512ce792 | 737 | } else { |
ac9b0805 | 738 | $sql = "SELECT u.id, u.id AS userid, AVG(g.grade) AS rawgrade |
646fc290 | 739 | FROM {user} u, {lesson_grades} g |
740 | WHERE u.id = g.userid AND g.lessonid = :lessonid | |
92bcca38 | 741 | $user |
742 | GROUP BY u.id"; | |
512ce792 | 743 | } |
96fa52a4 | 744 | unset($params['lessonid2']); |
745 | unset($params['userid2']); | |
512ce792 | 746 | } else { |
92bcca38 | 747 | // use only first attempts (with lowest id in lesson_grades table) |
748 | $firstonly = "SELECT uu.id AS userid, MIN(gg.id) AS firstcompleted | |
646fc290 | 749 | FROM {user} uu, {lesson_grades} gg |
b920f6e8 | 750 | WHERE uu.id = gg.userid AND gg.lessonid = :lessonid2 |
92bcca38 | 751 | $fuser |
752 | GROUP BY uu.id"; | |
753 | ||
ac9b0805 | 754 | $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade |
646fc290 | 755 | FROM {user} u, {lesson_grades} g, ($firstonly) f |
756 | WHERE u.id = g.userid AND g.lessonid = :lessonid | |
92bcca38 | 757 | AND g.id = f.firstcompleted AND g.userid=f.userid |
758 | $user"; | |
759 | } | |
760 | ||
646fc290 | 761 | return $DB->get_records_sql($sql, $params); |
92bcca38 | 762 | } |
763 | ||
764 | /** | |
765 | * Update grades in central gradebook | |
766 | * | |
a153c9f2 | 767 | * @category grade |
775f811a | 768 | * @param object $lesson |
769 | * @param int $userid specific user only, 0 means all | |
8cc86111 | 770 | * @param bool $nullifnone |
92bcca38 | 771 | */ |
775f811a | 772 | function lesson_update_grades($lesson, $userid=0, $nullifnone=true) { |
646fc290 | 773 | global $CFG, $DB; |
775f811a | 774 | require_once($CFG->libdir.'/gradelib.php'); |
92bcca38 | 775 | |
c7d1a37b | 776 | if ($lesson->grade == 0 || $lesson->practice) { |
775f811a | 777 | lesson_grade_item_update($lesson); |
92bcca38 | 778 | |
775f811a | 779 | } else if ($grades = lesson_get_user_grades($lesson, $userid)) { |
780 | lesson_grade_item_update($lesson, $grades); | |
eafb9d9e | 781 | |
775f811a | 782 | } else if ($userid and $nullifnone) { |
39790bd8 | 783 | $grade = new stdClass(); |
775f811a | 784 | $grade->userid = $userid; |
ecea65ca | 785 | $grade->rawgrade = null; |
775f811a | 786 | lesson_grade_item_update($lesson, $grade); |
92bcca38 | 787 | |
788 | } else { | |
775f811a | 789 | lesson_grade_item_update($lesson); |
790 | } | |
791 | } | |
792 | ||
92bcca38 | 793 | /** |
794 | * Create grade item for given lesson | |
795 | * | |
a153c9f2 | 796 | * @category grade |
8cc86111 | 797 | * @uses GRADE_TYPE_VALUE |
798 | * @uses GRADE_TYPE_NONE | |
92bcca38 | 799 | * @param object $lesson object with extra cmidnumber |
8cc86111 | 800 | * @param array|object $grades optional array/object of grade(s); 'reset' means reset grades in gradebook |
92bcca38 | 801 | * @return int 0 if ok, error code otherwise |
802 | */ | |
ecea65ca | 803 | function lesson_grade_item_update($lesson, $grades=null) { |
92bcca38 | 804 | global $CFG; |
805 | if (!function_exists('grade_update')) { //workaround for buggy PHP versions | |
806 | require_once($CFG->libdir.'/gradelib.php'); | |
807 | } | |
808 | ||
809 | if (array_key_exists('cmidnumber', $lesson)) { //it may not be always present | |
810 | $params = array('itemname'=>$lesson->name, 'idnumber'=>$lesson->cmidnumber); | |
ac8e16be | 811 | } else { |
92bcca38 | 812 | $params = array('itemname'=>$lesson->name); |
ac8e16be | 813 | } |
92bcca38 | 814 | |
33ce9e58 | 815 | if (!$lesson->practice and $lesson->grade > 0) { |
92bcca38 | 816 | $params['gradetype'] = GRADE_TYPE_VALUE; |
91ca18e8 | 817 | $params['grademax'] = $lesson->grade; |
92bcca38 | 818 | $params['grademin'] = 0; |
33ce9e58 | 819 | } else if (!$lesson->practice and $lesson->grade < 0) { |
f51cda3d AD |
820 | $params['gradetype'] = GRADE_TYPE_SCALE; |
821 | $params['scaleid'] = -$lesson->grade; | |
da7e5daf | 822 | |
16c25753 TL |
823 | // Make sure current grade fetched correctly from $grades |
824 | $currentgrade = null; | |
74dd3bd6 | 825 | if (!empty($grades)) { |
16c25753 TL |
826 | if (is_array($grades)) { |
827 | $currentgrade = reset($grades); | |
828 | } else { | |
829 | $currentgrade = $grades; | |
830 | } | |
831 | } | |
832 | ||
833 | // When converting a score to a scale, use scale's grade maximum to calculate it. | |
834 | if (!empty($currentgrade) && $currentgrade->rawgrade !== null) { | |
835 | $grade = grade_get_grades($lesson->course, 'mod', 'lesson', $lesson->id, $currentgrade->userid); | |
74dd3bd6 TL |
836 | $params['grademax'] = reset($grade->items)->grademax; |
837 | } | |
92bcca38 | 838 | } else { |
839 | $params['gradetype'] = GRADE_TYPE_NONE; | |
92bcca38 | 840 | } |
841 | ||
0b5a80a1 | 842 | if ($grades === 'reset') { |
843 | $params['reset'] = true; | |
ecea65ca | 844 | $grades = null; |
13b92708 | 845 | } else if (!empty($grades)) { |
846 | // Need to calculate raw grade (Note: $grades has many forms) | |
847 | if (is_object($grades)) { | |
848 | $grades = array($grades->userid => $grades); | |
849 | } else if (array_key_exists('userid', $grades)) { | |
850 | $grades = array($grades['userid'] => $grades); | |
851 | } | |
852 | foreach ($grades as $key => $grade) { | |
853 | if (!is_array($grade)) { | |
854 | $grades[$key] = $grade = (array) $grade; | |
855 | } | |
76027a9f AD |
856 | //check raw grade isnt null otherwise we erroneously insert a grade of 0 |
857 | if ($grade['rawgrade'] !== null) { | |
da7e5daf | 858 | $grades[$key]['rawgrade'] = ($grade['rawgrade'] * $params['grademax'] / 100); |
76027a9f AD |
859 | } else { |
860 | //setting rawgrade to null just in case user is deleting a grade | |
861 | $grades[$key]['rawgrade'] = null; | |
862 | } | |
13b92708 | 863 | } |
0b5a80a1 | 864 | } |
865 | ||
866 | return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, $grades, $params); | |
92bcca38 | 867 | } |
868 | ||
8cc86111 | 869 | /** |
b2b4ec30 RT |
870 | * List the actions that correspond to a view of this module. |
871 | * This is used by the participation report. | |
872 | * | |
873 | * Note: This is not used by new logging system. Event with | |
874 | * crud = 'r' and edulevel = LEVEL_PARTICIPATING will | |
875 | * be considered as view action. | |
876 | * | |
8cc86111 | 877 | * @return array |
878 | */ | |
f3221af9 | 879 | function lesson_get_view_actions() { |
cf051cc4 | 880 | return array('view','view all'); |
f3221af9 | 881 | } |
882 | ||
8cc86111 | 883 | /** |
b2b4ec30 RT |
884 | * List the actions that correspond to a post of this module. |
885 | * This is used by the participation report. | |
886 | * | |
887 | * Note: This is not used by new logging system. Event with | |
888 | * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING | |
889 | * will be considered as post action. | |
890 | * | |
8cc86111 | 891 | * @return array |
892 | */ | |
f3221af9 | 893 | function lesson_get_post_actions() { |
0a4abb73 | 894 | return array('end','start'); |
f3221af9 | 895 | } |
896 | ||
acf85537 | 897 | /** |
898 | * Runs any processes that must run before | |
899 | * a lesson insert/update | |
900 | * | |
8cc86111 | 901 | * @global object |
acf85537 | 902 | * @param object $lesson Lesson form data |
903 | * @return void | |
904 | **/ | |
905 | function lesson_process_pre_save(&$lesson) { | |
646fc290 | 906 | global $DB; |
fe75e76b | 907 | |
acf85537 | 908 | $lesson->timemodified = time(); |
909 | ||
a1acc001 JMV |
910 | if (empty($lesson->timelimit)) { |
911 | $lesson->timelimit = 0; | |
862bd9cd | 912 | } |
acf85537 | 913 | if (empty($lesson->timespent) or !is_numeric($lesson->timespent) or $lesson->timespent < 0) { |
914 | $lesson->timespent = 0; | |
915 | } | |
916 | if (!isset($lesson->completed)) { | |
917 | $lesson->completed = 0; | |
918 | } | |
919 | if (empty($lesson->gradebetterthan) or !is_numeric($lesson->gradebetterthan) or $lesson->gradebetterthan < 0) { | |
920 | $lesson->gradebetterthan = 0; | |
921 | } else if ($lesson->gradebetterthan > 100) { | |
922 | $lesson->gradebetterthan = 100; | |
923 | } | |
924 | ||
0a4abb73 SH |
925 | if (empty($lesson->width)) { |
926 | $lesson->width = 640; | |
927 | } | |
928 | if (empty($lesson->height)) { | |
929 | $lesson->height = 480; | |
930 | } | |
931 | if (empty($lesson->bgcolor)) { | |
932 | $lesson->bgcolor = '#FFFFFF'; | |
933 | } | |
934 | ||
acf85537 | 935 | // Conditions for dependency |
936 | $conditions = new stdClass; | |
937 | $conditions->timespent = $lesson->timespent; | |
938 | $conditions->completed = $lesson->completed; | |
939 | $conditions->gradebetterthan = $lesson->gradebetterthan; | |
646fc290 | 940 | $lesson->conditions = serialize($conditions); |
acf85537 | 941 | unset($lesson->timespent); |
942 | unset($lesson->completed); | |
943 | unset($lesson->gradebetterthan); | |
944 | ||
4d22ec81 | 945 | if (empty($lesson->password)) { |
acf85537 | 946 | unset($lesson->password); |
947 | } | |
acf85537 | 948 | } |
949 | ||
950 | /** | |
951 | * Runs any processes that must be run | |
952 | * after a lesson insert/update | |
953 | * | |
8cc86111 | 954 | * @global object |
acf85537 | 955 | * @param object $lesson Lesson form data |
956 | * @return void | |
957 | **/ | |
958 | function lesson_process_post_save(&$lesson) { | |
e0e1a83e JMV |
959 | // Update the events relating to this lesson. |
960 | lesson_update_events($lesson); | |
acf85537 | 961 | } |
962 | ||
0b5a80a1 | 963 | |
964 | /** | |
965 | * Implementation of the function for printing the form elements that control | |
966 | * whether the course reset functionality affects the lesson. | |
fe75e76b | 967 | * |
0b5a80a1 | 968 | * @param $mform form passed by reference |
969 | */ | |
970 | function lesson_reset_course_form_definition(&$mform) { | |
971 | $mform->addElement('header', 'lessonheader', get_string('modulenameplural', 'lesson')); | |
972 | $mform->addElement('advcheckbox', 'reset_lesson', get_string('deleteallattempts','lesson')); | |
e0e1a83e JMV |
973 | $mform->addElement('advcheckbox', 'reset_lesson_user_overrides', |
974 | get_string('removealluseroverrides', 'lesson')); | |
975 | $mform->addElement('advcheckbox', 'reset_lesson_group_overrides', | |
976 | get_string('removeallgroupoverrides', 'lesson')); | |
0b5a80a1 | 977 | } |
978 | ||
979 | /** | |
980 | * Course reset form defaults. | |
8cc86111 | 981 | * @param object $course |
982 | * @return array | |
0b5a80a1 | 983 | */ |
984 | function lesson_reset_course_form_defaults($course) { | |
e0e1a83e JMV |
985 | return array('reset_lesson' => 1, |
986 | 'reset_lesson_group_overrides' => 1, | |
987 | 'reset_lesson_user_overrides' => 1); | |
0b5a80a1 | 988 | } |
989 | ||
990 | /** | |
991 | * Removes all grades from gradebook | |
8cc86111 | 992 | * |
993 | * @global stdClass | |
994 | * @global object | |
0b5a80a1 | 995 | * @param int $courseid |
996 | * @param string optional type | |
997 | */ | |
998 | function lesson_reset_gradebook($courseid, $type='') { | |
646fc290 | 999 | global $CFG, $DB; |
0b5a80a1 | 1000 | |
1001 | $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid | |
646fc290 | 1002 | FROM {lesson} l, {course_modules} cm, {modules} m |
1003 | WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id AND l.course=:course"; | |
1004 | $params = array ("course" => $courseid); | |
1005 | if ($lessons = $DB->get_records_sql($sql,$params)) { | |
0b5a80a1 | 1006 | foreach ($lessons as $lesson) { |
1007 | lesson_grade_item_update($lesson, 'reset'); | |
1008 | } | |
1009 | } | |
1010 | } | |
1011 | ||
1012 | /** | |
72d2982e | 1013 | * Actual implementation of the reset course functionality, delete all the |
0b5a80a1 | 1014 | * lesson attempts for course $data->courseid. |
8cc86111 | 1015 | * |
1016 | * @global stdClass | |
1017 | * @global object | |
1018 | * @param object $data the data submitted from the reset course. | |
0b5a80a1 | 1019 | * @return array status array |
1020 | */ | |
1021 | function lesson_reset_userdata($data) { | |
646fc290 | 1022 | global $CFG, $DB; |
0b5a80a1 | 1023 | |
1024 | $componentstr = get_string('modulenameplural', 'lesson'); | |
1025 | $status = array(); | |
1026 | ||
1027 | if (!empty($data->reset_lesson)) { | |
1028 | $lessonssql = "SELECT l.id | |
646fc290 | 1029 | FROM {lesson} l |
1030 | WHERE l.course=:course"; | |
1031 | ||
1032 | $params = array ("course" => $data->courseid); | |
ebf5ad4f JMV |
1033 | $lessons = $DB->get_records_sql($lessonssql, $params); |
1034 | ||
1035 | // Get rid of attempts files. | |
1036 | $fs = get_file_storage(); | |
1037 | if ($lessons) { | |
1038 | foreach ($lessons as $lessonid => $unused) { | |
1039 | if (!$cm = get_coursemodule_from_instance('lesson', $lessonid)) { | |
1040 | continue; | |
1041 | } | |
1042 | $context = context_module::instance($cm->id); | |
1043 | $fs->delete_area_files($context->id, 'mod_lesson', 'essay_responses'); | |
1044 | } | |
1045 | } | |
1046 | ||
646fc290 | 1047 | $DB->delete_records_select('lesson_timer', "lessonid IN ($lessonssql)", $params); |
646fc290 | 1048 | $DB->delete_records_select('lesson_grades', "lessonid IN ($lessonssql)", $params); |
1049 | $DB->delete_records_select('lesson_attempts', "lessonid IN ($lessonssql)", $params); | |
91d90081 | 1050 | $DB->delete_records_select('lesson_branch', "lessonid IN ($lessonssql)", $params); |
0b5a80a1 | 1051 | |
1052 | // remove all grades from gradebook | |
1053 | if (empty($data->reset_gradebook_grades)) { | |
1054 | lesson_reset_gradebook($data->courseid); | |
1055 | } | |
1056 | ||
1057 | $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'lesson'), 'error'=>false); | |
1058 | } | |
1059 | ||
e0e1a83e JMV |
1060 | // Remove user overrides. |
1061 | if (!empty($data->reset_lesson_user_overrides)) { | |
1062 | $DB->delete_records_select('lesson_overrides', | |
1063 | 'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid)); | |
1064 | $status[] = array( | |
1065 | 'component' => $componentstr, | |
1066 | 'item' => get_string('useroverridesdeleted', 'lesson'), | |
1067 | 'error' => false); | |
1068 | } | |
1069 | // Remove group overrides. | |
1070 | if (!empty($data->reset_lesson_group_overrides)) { | |
1071 | $DB->delete_records_select('lesson_overrides', | |
1072 | 'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid)); | |
1073 | $status[] = array( | |
1074 | 'component' => $componentstr, | |
1075 | 'item' => get_string('groupoverridesdeleted', 'lesson'), | |
1076 | 'error' => false); | |
1077 | } | |
0b5a80a1 | 1078 | /// updating dates - shift may be negative too |
1079 | if ($data->timeshift) { | |
e0e1a83e JMV |
1080 | $DB->execute("UPDATE {lesson_overrides} |
1081 | SET available = available + ? | |
1082 | WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?) | |
1083 | AND available <> 0", array($data->timeshift, $data->courseid)); | |
1084 | $DB->execute("UPDATE {lesson_overrides} | |
1085 | SET deadline = deadline + ? | |
1086 | WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?) | |
1087 | AND deadline <> 0", array($data->timeshift, $data->courseid)); | |
1088 | ||
0b5a80a1 | 1089 | shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid); |
1090 | $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); | |
1091 | } | |
1092 | ||
1093 | return $status; | |
1094 | } | |
1095 | ||
f432bebf | 1096 | /** |
1097 | * Returns all other caps used in module | |
8cc86111 | 1098 | * @return array |
f432bebf | 1099 | */ |
1100 | function lesson_get_extra_capabilities() { | |
1101 | return array('moodle/site:accessallgroups'); | |
1102 | } | |
1103 | ||
18a2a0cb | 1104 | /** |
8cc86111 | 1105 | * @uses FEATURE_GROUPS |
1106 | * @uses FEATURE_GROUPINGS | |
8cc86111 | 1107 | * @uses FEATURE_MOD_INTRO |
1108 | * @uses FEATURE_COMPLETION_TRACKS_VIEWS | |
1109 | * @uses FEATURE_GRADE_HAS_GRADE | |
1110 | * @uses FEATURE_GRADE_OUTCOMES | |
18a2a0cb | 1111 | * @param string $feature FEATURE_xx constant for requested feature |
8cc86111 | 1112 | * @return mixed True if module supports feature, false if not, null if doesn't know |
18a2a0cb | 1113 | */ |
1114 | function lesson_supports($feature) { | |
1115 | switch($feature) { | |
d8389172 | 1116 | case FEATURE_GROUPS: |
fdc790fc | 1117 | return true; |
d8389172 | 1118 | case FEATURE_GROUPINGS: |
fdc790fc | 1119 | return true; |
d8389172 DNA |
1120 | case FEATURE_MOD_INTRO: |
1121 | return true; | |
1122 | case FEATURE_COMPLETION_TRACKS_VIEWS: | |
1123 | return true; | |
1124 | case FEATURE_GRADE_HAS_GRADE: | |
1125 | return true; | |
25345cb4 JMV |
1126 | case FEATURE_COMPLETION_HAS_RULES: |
1127 | return true; | |
d8389172 DNA |
1128 | case FEATURE_GRADE_OUTCOMES: |
1129 | return true; | |
1130 | case FEATURE_BACKUP_MOODLE2: | |
1131 | return true; | |
1132 | case FEATURE_SHOW_DESCRIPTION: | |
1133 | return true; | |
1134 | default: | |
1135 | return null; | |
18a2a0cb | 1136 | } |
1137 | } | |
c6949936 | 1138 | |
25345cb4 JMV |
1139 | /** |
1140 | * Obtains the automatic completion state for this lesson based on any conditions | |
1141 | * in lesson settings. | |
1142 | * | |
1143 | * @param object $course Course | |
1144 | * @param object $cm course-module | |
1145 | * @param int $userid User ID | |
1146 | * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) | |
1147 | * @return bool True if completed, false if not, $type if conditions not set. | |
1148 | */ | |
1149 | function lesson_get_completion_state($course, $cm, $userid, $type) { | |
1150 | global $CFG, $DB; | |
1151 | ||
1152 | // Get lesson details. | |
1153 | $lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*', | |
1154 | MUST_EXIST); | |
1155 | ||
d0445cf7 | 1156 | $result = $type; // Default return value. |
25345cb4 JMV |
1157 | // If completion option is enabled, evaluate it and return true/false. |
1158 | if ($lesson->completionendreached) { | |
d0445cf7 | 1159 | $value = $DB->record_exists('lesson_timer', array( |
25345cb4 | 1160 | 'lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1)); |
d0445cf7 JMV |
1161 | if ($type == COMPLETION_AND) { |
1162 | $result = $result && $value; | |
1163 | } else { | |
1164 | $result = $result || $value; | |
1165 | } | |
1166 | } | |
10e2355f | 1167 | if ($lesson->completiontimespent != 0) { |
d0445cf7 JMV |
1168 | $duration = $DB->get_field_sql( |
1169 | "SELECT SUM(lessontime - starttime) | |
1170 | FROM {lesson_timer} | |
1171 | WHERE lessonid = :lessonid | |
1172 | AND userid = :userid", | |
1173 | array('userid' => $userid, 'lessonid' => $lesson->id)); | |
1174 | if (!$duration) { | |
1175 | $duration = 0; | |
1176 | } | |
1177 | if ($type == COMPLETION_AND) { | |
10e2355f | 1178 | $result = $result && ($lesson->completiontimespent < $duration); |
d0445cf7 | 1179 | } else { |
10e2355f | 1180 | $result = $result || ($lesson->completiontimespent < $duration); |
d0445cf7 | 1181 | } |
25345cb4 | 1182 | } |
d0445cf7 | 1183 | return $result; |
25345cb4 | 1184 | } |
c6949936 | 1185 | /** |
1186 | * This function extends the settings navigation block for the site. | |
1187 | * | |
1188 | * It is safe to rely on PAGE here as we will only ever be within the module | |
1189 | * context when this is called | |
1190 | * | |
0b29477b SH |
1191 | * @param settings_navigation $settings |
1192 | * @param navigation_node $lessonnode | |
c6949936 | 1193 | */ |
0b29477b SH |
1194 | function lesson_extend_settings_navigation($settings, $lessonnode) { |
1195 | global $PAGE, $DB; | |
0a4abb73 | 1196 | |
e0e1a83e JMV |
1197 | // We want to add these new nodes after the Edit settings node, and before the |
1198 | // Locally assigned roles node. Of course, both of those are controlled by capabilities. | |
1199 | $keys = $lessonnode->get_children_key_list(); | |
1200 | $beforekey = null; | |
1201 | $i = array_search('modedit', $keys); | |
1202 | if ($i === false and array_key_exists(0, $keys)) { | |
1203 | $beforekey = $keys[0]; | |
1204 | } else if (array_key_exists($i + 1, $keys)) { | |
1205 | $beforekey = $keys[$i + 1]; | |
1206 | } | |
1207 | ||
1208 | if (has_capability('mod/lesson:manageoverrides', $PAGE->cm->context)) { | |
1209 | $url = new moodle_url('/mod/lesson/overrides.php', array('cmid' => $PAGE->cm->id)); | |
1210 | $node = navigation_node::create(get_string('groupoverrides', 'lesson'), | |
1211 | new moodle_url($url, array('mode' => 'group')), | |
1212 | navigation_node::TYPE_SETTING, null, 'mod_lesson_groupoverrides'); | |
1213 | $lessonnode->add_node($node, $beforekey); | |
1214 | ||
1215 | $node = navigation_node::create(get_string('useroverrides', 'lesson'), | |
1216 | new moodle_url($url, array('mode' => 'user')), | |
1217 | navigation_node::TYPE_SETTING, null, 'mod_lesson_useroverrides'); | |
1218 | $lessonnode->add_node($node, $beforekey); | |
1219 | } | |
1220 | ||
5fc6a9e7 | 1221 | if (has_capability('mod/lesson:edit', $PAGE->cm->context)) { |
43fd7949 JO |
1222 | $url = new moodle_url('/mod/lesson/view.php', array('id' => $PAGE->cm->id)); |
1223 | $lessonnode->add(get_string('preview', 'lesson'), $url); | |
02ff22e9 JO |
1224 | $editnode = $lessonnode->add(get_string('edit', 'lesson')); |
1225 | $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'collapsed')); | |
1226 | $editnode->add(get_string('collapsed', 'lesson'), $url); | |
1227 | $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'full')); | |
1228 | $editnode->add(get_string('full', 'lesson'), $url); | |
c6949936 | 1229 | } |
1230 | ||
b69d512a | 1231 | if (has_capability('mod/lesson:viewreports', $PAGE->cm->context)) { |
3406acde | 1232 | $reportsnode = $lessonnode->add(get_string('reports', 'lesson')); |
a6855934 | 1233 | $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportoverview')); |
3406acde | 1234 | $reportsnode->add(get_string('overview', 'lesson'), $url); |
a6855934 | 1235 | $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportdetail')); |
3406acde | 1236 | $reportsnode->add(get_string('detailedstats', 'lesson'), $url); |
c6949936 | 1237 | } |
1238 | ||
5fc6a9e7 | 1239 | if (has_capability('mod/lesson:grade', $PAGE->cm->context)) { |
a6855934 | 1240 | $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$PAGE->cm->id)); |
0b29477b | 1241 | $lessonnode->add(get_string('manualgrading', 'lesson'), $url); |
c6949936 | 1242 | } |
1243 | ||
3406acde | 1244 | } |
0a4abb73 SH |
1245 | |
1246 | /** | |
1247 | * Get list of available import or export formats | |
1248 | * | |
1249 | * Copied and modified from lib/questionlib.php | |
1250 | * | |
1251 | * @param string $type 'import' if import list, otherwise export list assumed | |
1252 | * @return array sorted list of import/export formats available | |
1253 | */ | |
1254 | function lesson_get_import_export_formats($type) { | |
1255 | global $CFG; | |
bd3b3bba | 1256 | $fileformats = core_component::get_plugin_list("qformat"); |
0a4abb73 SH |
1257 | |
1258 | $fileformatname=array(); | |
1259 | foreach ($fileformats as $fileformat=>$fdir) { | |
1260 | $format_file = "$fdir/format.php"; | |
1261 | if (file_exists($format_file) ) { | |
1262 | require_once($format_file); | |
1263 | } else { | |
1264 | continue; | |
1265 | } | |
1266 | $classname = "qformat_$fileformat"; | |
1267 | $format_class = new $classname(); | |
1268 | if ($type=='import') { | |
1269 | $provided = $format_class->provide_import(); | |
1270 | } else { | |
1271 | $provided = $format_class->provide_export(); | |
1272 | } | |
1273 | if ($provided) { | |
64679179 | 1274 | $fileformatnames[$fileformat] = get_string('pluginname', 'qformat_'.$fileformat); |
0a4abb73 SH |
1275 | } |
1276 | } | |
1277 | natcasesort($fileformatnames); | |
1278 | ||
1279 | return $fileformatnames; | |
1280 | } | |
1281 | ||
1282 | /** | |
1283 | * Serves the lesson attachments. Implements needed access control ;-) | |
1284 | * | |
d2b7803e DC |
1285 | * @package mod_lesson |
1286 | * @category files | |
1287 | * @param stdClass $course course object | |
1288 | * @param stdClass $cm course module object | |
1289 | * @param stdClass $context context object | |
1290 | * @param string $filearea file area | |
1291 | * @param array $args extra arguments | |
1292 | * @param bool $forcedownload whether or not force download | |
261cbbac | 1293 | * @param array $options additional options affecting the file serving |
0a4abb73 SH |
1294 | * @return bool false if file not found, does not return if found - justsend the file |
1295 | */ | |
261cbbac | 1296 | function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { |
0a4abb73 SH |
1297 | global $CFG, $DB; |
1298 | ||
64f93798 | 1299 | if ($context->contextlevel != CONTEXT_MODULE) { |
0a4abb73 SH |
1300 | return false; |
1301 | } | |
2f67a9b3 | 1302 | |
0a4abb73 SH |
1303 | $fileareas = lesson_get_file_areas(); |
1304 | if (!array_key_exists($filearea, $fileareas)) { | |
1305 | return false; | |
1306 | } | |
1307 | ||
64f93798 | 1308 | if (!$lesson = $DB->get_record('lesson', array('id'=>$cm->instance))) { |
0a4abb73 SH |
1309 | return false; |
1310 | } | |
1311 | ||
1312 | require_course_login($course, true, $cm); | |
1313 | ||
64f93798 | 1314 | if ($filearea === 'page_contents') { |
0a4abb73 SH |
1315 | $pageid = (int)array_shift($args); |
1316 | if (!$page = $DB->get_record('lesson_pages', array('id'=>$pageid))) { | |
1317 | return false; | |
1318 | } | |
64f93798 | 1319 | $fullpath = "/$context->id/mod_lesson/$filearea/$pageid/".implode('/', $args); |
64f93798 | 1320 | |
0abc18cf JMV |
1321 | } else if ($filearea === 'page_answers' || $filearea === 'page_responses') { |
1322 | $itemid = (int)array_shift($args); | |
1323 | if (!$pageanswers = $DB->get_record('lesson_answers', array('id' => $itemid))) { | |
1324 | return false; | |
1325 | } | |
1326 | $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args); | |
1327 | ||
ebf5ad4f JMV |
1328 | } else if ($filearea === 'essay_responses') { |
1329 | $itemid = (int)array_shift($args); | |
1330 | if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $itemid))) { | |
1331 | return false; | |
1332 | } | |
1333 | $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args); | |
1334 | ||
3d1b7452 | 1335 | } else if ($filearea === 'mediafile') { |
63fe00c9 FM |
1336 | if (count($args) > 1) { |
1337 | // Remove the itemid when it appears to be part of the arguments. If there is only one argument | |
1338 | // then it is surely the file name. The itemid is sometimes used to prevent browser caching. | |
1339 | array_shift($args); | |
1340 | } | |
3d1b7452 PS |
1341 | $fullpath = "/$context->id/mod_lesson/$filearea/0/".implode('/', $args); |
1342 | ||
1343 | } else { | |
1344 | return false; | |
0a4abb73 SH |
1345 | } |
1346 | ||
1347 | $fs = get_file_storage(); | |
1348 | if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { | |
1349 | return false; | |
1350 | } | |
1351 | ||
1352 | // finally send the file | |
261cbbac | 1353 | send_stored_file($file, 0, 0, $forcedownload, $options); // download MUST be forced - security! |
0a4abb73 SH |
1354 | } |
1355 | ||
1356 | /** | |
1357 | * Returns an array of file areas | |
d2b7803e DC |
1358 | * |
1359 | * @package mod_lesson | |
1360 | * @category files | |
d2b7803e | 1361 | * @return array a list of available file areas |
0a4abb73 SH |
1362 | */ |
1363 | function lesson_get_file_areas() { | |
64f93798 | 1364 | $areas = array(); |
89133afa FM |
1365 | $areas['page_contents'] = get_string('pagecontents', 'mod_lesson'); |
1366 | $areas['mediafile'] = get_string('mediafile', 'mod_lesson'); | |
0abc18cf JMV |
1367 | $areas['page_answers'] = get_string('pageanswers', 'mod_lesson'); |
1368 | $areas['page_responses'] = get_string('pageresponses', 'mod_lesson'); | |
ebf5ad4f | 1369 | $areas['essay_responses'] = get_string('essayresponses', 'mod_lesson'); |
3d1b7452 | 1370 | return $areas; |
0a4abb73 SH |
1371 | } |
1372 | ||
1373 | /** | |
1374 | * Returns a file_info_stored object for the file being requested here | |
1375 | * | |
d2b7803e DC |
1376 | * @package mod_lesson |
1377 | * @category files | |
1378 | * @global stdClass $CFG | |
1379 | * @param file_browse $browser file browser instance | |
1380 | * @param array $areas file areas | |
1381 | * @param stdClass $course course object | |
1382 | * @param stdClass $cm course module object | |
1383 | * @param stdClass $context context object | |
1384 | * @param string $filearea file area | |
1385 | * @param int $itemid item ID | |
1386 | * @param string $filepath file path | |
1387 | * @param string $filename file name | |
0a4abb73 SH |
1388 | * @return file_info_stored |
1389 | */ | |
1390 | function lesson_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { | |
b47dc5ff FM |
1391 | global $CFG, $DB; |
1392 | ||
1393 | if (!has_capability('moodle/course:managefiles', $context)) { | |
1394 | // No peaking here for students! | |
64f93798 PS |
1395 | return null; |
1396 | } | |
1397 | ||
b47dc5ff FM |
1398 | // Mediafile area does not have sub directories, so let's select the default itemid to prevent |
1399 | // the user from selecting a directory to access the mediafile content. | |
1400 | if ($filearea == 'mediafile' && is_null($itemid)) { | |
1401 | $itemid = 0; | |
1402 | } | |
1403 | ||
1404 | if (is_null($itemid)) { | |
b47dc5ff FM |
1405 | return new mod_lesson_file_info($browser, $course, $cm, $context, $areas, $filearea); |
1406 | } | |
1407 | ||
0a4abb73 SH |
1408 | $fs = get_file_storage(); |
1409 | $filepath = is_null($filepath) ? '/' : $filepath; | |
1410 | $filename = is_null($filename) ? '.' : $filename; | |
64f93798 | 1411 | if (!$storedfile = $fs->get_file($context->id, 'mod_lesson', $filearea, $itemid, $filepath, $filename)) { |
0a4abb73 SH |
1412 | return null; |
1413 | } | |
b47dc5ff FM |
1414 | |
1415 | $itemname = $filearea; | |
1416 | if ($filearea == 'page_contents') { | |
1417 | $itemname = $DB->get_field('lesson_pages', 'title', array('lessonid' => $cm->instance, 'id' => $itemid)); | |
1418 | $itemname = format_string($itemname, true, array('context' => $context)); | |
1419 | } else { | |
1420 | $areas = lesson_get_file_areas(); | |
1421 | if (isset($areas[$filearea])) { | |
1422 | $itemname = $areas[$filearea]; | |
1423 | } | |
1424 | } | |
1425 | ||
1426 | $urlbase = $CFG->wwwroot . '/pluginfile.php'; | |
1427 | return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemname, $itemid, true, true, false); | |
0a4abb73 | 1428 | } |
b1627a92 DC |
1429 | |
1430 | ||
1431 | /** | |
1432 | * Return a list of page types | |
1433 | * @param string $pagetype current page type | |
1434 | * @param stdClass $parentcontext Block's parent context | |
1435 | * @param stdClass $currentcontext Current context of block | |
1436 | */ | |
b38e2e28 | 1437 | function lesson_page_type_list($pagetype, $parentcontext, $currentcontext) { |
346a32a7 AD |
1438 | $module_pagetype = array( |
1439 | 'mod-lesson-*'=>get_string('page-mod-lesson-x', 'lesson'), | |
1440 | 'mod-lesson-view'=>get_string('page-mod-lesson-view', 'lesson'), | |
1441 | 'mod-lesson-edit'=>get_string('page-mod-lesson-edit', 'lesson')); | |
b1627a92 DC |
1442 | return $module_pagetype; |
1443 | } | |
684c3be7 MN |
1444 | |
1445 | /** | |
1446 | * Update the lesson activity to include any file | |
1447 | * that was uploaded, or if there is none, set the | |
1448 | * mediafile field to blank. | |
1449 | * | |
1450 | * @param int $lessonid the lesson id | |
1451 | * @param stdClass $context the context | |
1452 | * @param int $draftitemid the draft item | |
1453 | */ | |
1454 | function lesson_update_media_file($lessonid, $context, $draftitemid) { | |
1455 | global $DB; | |
1456 | ||
1457 | // Set the filestorage object. | |
1458 | $fs = get_file_storage(); | |
1459 | // Save the file if it exists that is currently in the draft area. | |
1460 | file_save_draft_area_files($draftitemid, $context->id, 'mod_lesson', 'mediafile', 0); | |
1461 | // Get the file if it exists. | |
1462 | $files = $fs->get_area_files($context->id, 'mod_lesson', 'mediafile', 0, 'itemid, filepath, filename', false); | |
1463 | // Check that there is a file to process. | |
1464 | if (count($files) == 1) { | |
1465 | // Get the first (and only) file. | |
1466 | $file = reset($files); | |
1467 | // Set the mediafile column in the lessons table. | |
1468 | $DB->set_field('lesson', 'mediafile', '/' . $file->get_filename(), array('id' => $lessonid)); | |
1469 | } else { | |
1470 | // Set the mediafile column in the lessons table. | |
1471 | $DB->set_field('lesson', 'mediafile', '', array('id' => $lessonid)); | |
1472 | } | |
1473 | } |