Fixes to the PaintWeb cron task.
[moodle/mihaisucan.git] / mod / lesson / lib.php
blob37cb87dd880f79e9c5e5a23083f21569f1b251fa
1 <?php // $Id$
2 /**
3 * Standard library of functions and constants for lesson
5 * @version $Id$
6 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7 * @package lesson
8 **/
10 define("LESSON_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
12 /**
13 * Given an object containing all the necessary data,
14 * (defined by the form in mod_form.php) this function
15 * will create a new instance and return the id number
16 * of the new instance.
18 * @param object $lesson Lesson post data from the form
19 * @return int
20 **/
21 function lesson_add_instance($lesson) {
22 global $SESSION;
24 lesson_process_pre_save($lesson);
26 if (!$lesson->id = insert_record("lesson", $lesson)) {
27 return false; // bad
30 lesson_process_post_save($lesson);
32 lesson_grade_item_update(stripslashes_recursive($lesson));
34 return $lesson->id;
37 /**
38 * Given an object containing all the necessary data,
39 * (defined by the form in mod_form.php) this function
40 * will update an existing instance with new data.
42 * @param object $lesson Lesson post data from the form
43 * @return boolean
44 **/
45 function lesson_update_instance($lesson) {
47 $lesson->id = $lesson->instance;
49 lesson_process_pre_save($lesson);
51 if (!$result = update_record("lesson", $lesson)) {
52 return false; // Awe man!
55 lesson_process_post_save($lesson);
57 // update grade item definition
58 lesson_grade_item_update(stripslashes_recursive($lesson));
60 // update grades - TODO: do it only when grading style changes
61 lesson_update_grades(stripslashes_recursive($lesson), 0, false);
63 return $result;
67 /*******************************************************************/
68 function lesson_delete_instance($id) {
69 /// Given an ID of an instance of this module,
70 /// this function will permanently delete the instance
71 /// and any data that depends on it.
73 if (! $lesson = get_record("lesson", "id", "$id")) {
74 return false;
77 $result = true;
79 if (! delete_records("lesson", "id", "$lesson->id")) {
80 $result = false;
82 if (! delete_records("lesson_pages", "lessonid", "$lesson->id")) {
83 $result = false;
85 if (! delete_records("lesson_answers", "lessonid", "$lesson->id")) {
86 $result = false;
88 if (! delete_records("lesson_attempts", "lessonid", "$lesson->id")) {
89 $result = false;
91 if (! delete_records("lesson_grades", "lessonid", "$lesson->id")) {
92 $result = false;
94 if (! delete_records("lesson_timer", "lessonid", "$lesson->id")) {
95 $result = false;
97 if (! delete_records("lesson_branch", "lessonid", "$lesson->id")) {
98 $result = false;
100 if (! delete_records("lesson_high_scores", "lessonid", "$lesson->id")) {
101 $result = false;
103 if ($events = get_records_select('event', "modulename = 'lesson' and instance = '$lesson->id'")) {
104 foreach($events as $event) {
105 delete_event($event->id);
108 $pagetypes = page_import_types('mod/lesson/');
109 foreach ($pagetypes as $pagetype) {
110 if (!blocks_delete_all_on_page($pagetype, $lesson->id)) {
111 $result = false;
115 lesson_grade_item_delete($lesson);
117 return $result;
121 * Given a course object, this function will clean up anything that
122 * would be leftover after all the instances were deleted.
124 * As of now, this function just cleans the lesson_default table
126 * @param object $course an object representing the course that is being deleted
127 * @param boolean $feedback to specify if the process must output a summary of its work
128 * @return boolean
130 function lesson_delete_course($course, $feedback=true) {
132 $count = count_records('lesson_default', 'course', $course->id);
133 delete_records('lesson_default', 'course', $course->id);
135 //Inform about changes performed if feedback is enabled
136 if ($feedback) {
137 notify(get_string('deletedefaults', 'lesson', $count));
140 return true;
143 /*******************************************************************/
144 function lesson_user_outline($course, $user, $mod, $lesson) {
145 /// Return a small object with summary information about what a
146 /// user has done with a given particular instance of this module
147 /// Used for user activity reports.
148 /// $return->time = the time they did it
149 /// $return->info = a short text description
150 global $CFG;
151 require_once("$CFG->libdir/gradelib.php");
152 $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
153 if (empty($grades->items[0]->grades)) {
154 $return->info = get_string("no")." ".get_string("attempts", "lesson");
155 } else {
156 $grade = reset($grades->items[0]->grades);
157 $return->info = get_string("grade") . ': ' . $grade->str_long_grade;
158 $return->time = $grade->dategraded;
160 return $return;
163 /*******************************************************************/
164 function lesson_user_complete($course, $user, $mod, $lesson) {
165 /// Print a detailed representation of what a user has done with
166 /// a given particular instance of this module, for user activity reports.
167 global $CFG;
168 require_once("$CFG->libdir/gradelib.php");
170 $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
171 if (!empty($grades->items[0]->grades)) {
172 $grade = reset($grades->items[0]->grades);
173 echo '<p>'.get_string('grade').': '.$grade->str_long_grade.'</p>';
174 if ($grade->str_feedback) {
175 echo '<p>'.get_string('feedback').': '.$grade->str_feedback.'</p>';
179 if ($attempts = get_records_select("lesson_attempts", "lessonid = $lesson->id AND userid = $user->id",
180 "retry, timeseen")) {
181 print_simple_box_start();
182 $table->head = array (get_string("attempt", "lesson"), get_string("numberofpagesviewed", "lesson"),
183 get_string("numberofcorrectanswers", "lesson"), get_string("time"));
184 $table->width = "100%";
185 $table->align = array ("center", "center", "center", "center");
186 $table->size = array ("*", "*", "*", "*");
187 $table->cellpadding = 2;
188 $table->cellspacing = 0;
190 $retry = 0;
191 $npages = 0;
192 $ncorrect = 0;
194 foreach ($attempts as $attempt) {
195 if ($attempt->retry == $retry) {
196 $npages++;
197 if ($attempt->correct) {
198 $ncorrect++;
200 $timeseen = $attempt->timeseen;
201 } else {
202 $table->data[] = array($retry + 1, $npages, $ncorrect, userdate($timeseen));
203 $retry++;
204 $npages = 1;
205 if ($attempt->correct) {
206 $ncorrect = 1;
207 } else {
208 $ncorrect = 0;
212 if ($npages) {
213 $table->data[] = array($retry + 1, $npages, $ncorrect, userdate($timeseen));
215 print_table($table);
216 print_simple_box_end();
219 return true;
223 * Prints lesson summaries on MyMoodle Page
225 * Prints lesson name, due date and attempt information on
226 * lessons that have a deadline that has not already passed
227 * and it is available for taking.
229 * @param array $courses An array of course objects to get lesson instances from
230 * @param array $htmlarray Store overview output array( course ID => 'lesson' => HTML output )
232 function lesson_print_overview($courses, &$htmlarray) {
233 global $USER, $CFG;
235 if (!$lessons = get_all_instances_in_courses('lesson', $courses)) {
236 return;
239 /// Get Necessary Strings
240 $strlesson = get_string('modulename', 'lesson');
241 $strnotattempted = get_string('nolessonattempts', 'lesson');
242 $strattempted = get_string('lessonattempted', 'lesson');
244 $now = time();
245 foreach ($lessons as $lesson) {
246 if ($lesson->deadline != 0 // The lesson has a deadline
247 and $lesson->deadline >= $now // And it is before the deadline has been met
248 and ($lesson->available == 0 or $lesson->available <= $now)) { // And the lesson is available
250 // Lesson name
251 if (!$lesson->visible) {
252 $class = ' class="dimmed"';
253 } else {
254 $class = '';
256 $str = print_box("$strlesson: <a$class href=\"$CFG->wwwroot/mod/lesson/view.php?id=$lesson->coursemodule\">".
257 format_string($lesson->name).'</a>', 'name', '', true);
259 // Deadline
260 $str .= print_box(get_string('lessoncloseson', 'lesson', userdate($lesson->deadline)), 'info', '', true);
262 // Attempt information
263 if (has_capability('mod/lesson:manage', get_context_instance(CONTEXT_MODULE, $lesson->coursemodule))) {
264 // Number of user attempts
265 $attempts = count_records('lesson_attempts', 'lessonid', $lesson->id);
266 $str .= print_box(get_string('xattempts', 'lesson', $attempts), 'info', '', true);
267 } else {
268 // Determine if the user has attempted the lesson or not
269 if (count_records('lesson_attempts', 'lessonid', $lesson->id, 'userid', $USER->id)) {
270 $str .= print_box($strattempted, 'info', '', true);
271 } else {
272 $str .= print_box($strnotattempted, 'info', '', true);
275 $str = print_box($str, 'lesson overview', '', true);
277 if (empty($htmlarray[$lesson->course]['lesson'])) {
278 $htmlarray[$lesson->course]['lesson'] = $str;
279 } else {
280 $htmlarray[$lesson->course]['lesson'] .= $str;
286 /*******************************************************************/
287 function lesson_cron () {
288 /// Function to be run periodically according to the moodle cron
289 /// This function searches for things that need to be done, such
290 /// as sending out mail, toggling flags etc ...
292 global $CFG;
294 return true;
298 * Return grade for given user or all users.
300 * @param int $lessonid id of lesson
301 * @param int $userid optional user id, 0 means all users
302 * @return array array of grades, false if none
304 function lesson_get_user_grades($lesson, $userid=0) {
305 global $CFG;
307 $user = $userid ? "AND u.id = $userid" : "";
308 $fuser = $userid ? "AND uu.id = $userid" : "";
310 if ($lesson->retake) {
311 if ($lesson->usemaxgrade) {
312 $sql = "SELECT u.id, u.id AS userid, MAX(g.grade) AS rawgrade
313 FROM {$CFG->prefix}user u, {$CFG->prefix}lesson_grades g
314 WHERE u.id = g.userid AND g.lessonid = $lesson->id
315 $user
316 GROUP BY u.id";
317 } else {
318 $sql = "SELECT u.id, u.id AS userid, AVG(g.grade) AS rawgrade
319 FROM {$CFG->prefix}user u, {$CFG->prefix}lesson_grades g
320 WHERE u.id = g.userid AND g.lessonid = $lesson->id
321 $user
322 GROUP BY u.id";
325 } else {
326 // use only first attempts (with lowest id in lesson_grades table)
327 $firstonly = "SELECT uu.id AS userid, MIN(gg.id) AS firstcompleted
328 FROM {$CFG->prefix}user uu, {$CFG->prefix}lesson_grades gg
329 WHERE uu.id = gg.userid AND gg.lessonid = $lesson->id
330 $fuser
331 GROUP BY uu.id";
333 $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade
334 FROM {$CFG->prefix}user u, {$CFG->prefix}lesson_grades g, ($firstonly) f
335 WHERE u.id = g.userid AND g.lessonid = $lesson->id
336 AND g.id = f.firstcompleted AND g.userid=f.userid
337 $user";
340 return get_records_sql($sql);
344 * Update grades in central gradebook
346 * @param object $lesson null means all lessons
347 * @param int $userid specific user only, 0 mean all
349 function lesson_update_grades($lesson=null, $userid=0, $nullifnone=true) {
350 global $CFG;
351 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
352 require_once($CFG->libdir.'/gradelib.php');
355 if ($lesson != null) {
356 if ($grades = lesson_get_user_grades($lesson, $userid)) {
357 lesson_grade_item_update($lesson, $grades);
359 } else if ($userid and $nullifnone) {
360 $grade = new object();
361 $grade->userid = $userid;
362 $grade->rawgrade = NULL;
363 lesson_grade_item_update($lesson, $grade);
365 } else {
366 lesson_grade_item_update($lesson);
369 } else {
370 $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid
371 FROM {$CFG->prefix}lesson l, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
372 WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id";
373 if ($rs = get_recordset_sql($sql)) {
374 while ($lesson = rs_fetch_next_record($rs)) {
375 if ($lesson->grade != 0) {
376 lesson_update_grades($lesson, 0, false);
377 } else {
378 lesson_grade_item_update($lesson);
381 rs_close($rs);
387 * Create grade item for given lesson
389 * @param object $lesson object with extra cmidnumber
390 * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
391 * @return int 0 if ok, error code otherwise
393 function lesson_grade_item_update($lesson, $grades=NULL) {
394 global $CFG;
395 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
396 require_once($CFG->libdir.'/gradelib.php');
399 if (array_key_exists('cmidnumber', $lesson)) { //it may not be always present
400 $params = array('itemname'=>$lesson->name, 'idnumber'=>$lesson->cmidnumber);
401 } else {
402 $params = array('itemname'=>$lesson->name);
405 if ($lesson->grade > 0) {
406 $params['gradetype'] = GRADE_TYPE_VALUE;
407 $params['grademax'] = $lesson->grade;
408 $params['grademin'] = 0;
410 } else {
411 $params['gradetype'] = GRADE_TYPE_NONE;
414 if ($grades === 'reset') {
415 $params['reset'] = true;
416 $grades = NULL;
417 } else if (!empty($grades)) {
418 // Need to calculate raw grade (Note: $grades has many forms)
419 if (is_object($grades)) {
420 $grades = array($grades->userid => $grades);
421 } else if (array_key_exists('userid', $grades)) {
422 $grades = array($grades['userid'] => $grades);
424 foreach ($grades as $key => $grade) {
425 if (!is_array($grade)) {
426 $grades[$key] = $grade = (array) $grade;
428 $grades[$key]['rawgrade'] = ($grade['rawgrade'] * $lesson->grade / 100);
432 return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, $grades, $params);
436 * Delete grade item for given lesson
438 * @param object $lesson object
439 * @return object lesson
441 function lesson_grade_item_delete($lesson) {
442 global $CFG;
443 require_once($CFG->libdir.'/gradelib.php');
445 return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, NULL, array('deleted'=>1));
449 /*******************************************************************/
450 function lesson_get_participants($lessonid) {
451 //Must return an array of user records (all data) who are participants
452 //for a given instance of lesson. Must include every user involved
453 //in the instance, independient of his role (student, teacher, admin...)
455 global $CFG;
457 //Get students
458 $students = get_records_sql("SELECT DISTINCT u.id, u.id
459 FROM {$CFG->prefix}user u,
460 {$CFG->prefix}lesson_attempts a
461 WHERE a.lessonid = '$lessonid' and
462 u.id = a.userid");
464 //Return students array (it contains an array of unique users)
465 return ($students);
468 function lesson_get_view_actions() {
469 return array('view','view all');
472 function lesson_get_post_actions() {
473 return array('end','start', 'update grade attempt');
477 * Runs any processes that must run before
478 * a lesson insert/update
480 * @param object $lesson Lesson form data
481 * @return void
483 function lesson_process_pre_save(&$lesson) {
484 $lesson->timemodified = time();
486 if (empty($lesson->timed)) {
487 $lesson->timed = 0;
489 if (empty($lesson->timespent) or !is_numeric($lesson->timespent) or $lesson->timespent < 0) {
490 $lesson->timespent = 0;
492 if (!isset($lesson->completed)) {
493 $lesson->completed = 0;
495 if (empty($lesson->gradebetterthan) or !is_numeric($lesson->gradebetterthan) or $lesson->gradebetterthan < 0) {
496 $lesson->gradebetterthan = 0;
497 } else if ($lesson->gradebetterthan > 100) {
498 $lesson->gradebetterthan = 100;
501 // Conditions for dependency
502 $conditions = new stdClass;
503 $conditions->timespent = $lesson->timespent;
504 $conditions->completed = $lesson->completed;
505 $conditions->gradebetterthan = $lesson->gradebetterthan;
506 $lesson->conditions = addslashes(serialize($conditions));
507 unset($lesson->timespent);
508 unset($lesson->completed);
509 unset($lesson->gradebetterthan);
511 if (empty($lesson->password)) {
512 unset($lesson->password);
515 if ($lesson->lessondefault) {
516 $default = new stdClass;
517 $default = clone($lesson);
518 unset($default->name);
519 unset($default->timemodified);
520 unset($default->available);
521 unset($default->deadline);
522 if ($default->id = get_field('lesson_default', 'id', 'course', $default->course)) {
523 update_record('lesson_default', $default);
524 } else {
525 insert_record('lesson_default', $default);
528 unset($lesson->lessondefault);
532 * Runs any processes that must be run
533 * after a lesson insert/update
535 * @param object $lesson Lesson form data
536 * @return void
538 function lesson_process_post_save(&$lesson) {
539 if ($events = get_records_select('event', "modulename = 'lesson' and instance = '$lesson->id'")) {
540 foreach($events as $event) {
541 delete_event($event->id);
545 $event = new stdClass;
546 $event->description = $lesson->name;
547 $event->courseid = $lesson->course;
548 $event->groupid = 0;
549 $event->userid = 0;
550 $event->modulename = 'lesson';
551 $event->instance = $lesson->id;
552 $event->eventtype = 'open';
553 $event->timestart = $lesson->available;
554 $event->visible = instance_is_visible('lesson', $lesson);
555 $event->timeduration = ($lesson->deadline - $lesson->available);
557 if ($lesson->deadline and $lesson->available and $event->timeduration <= LESSON_MAX_EVENT_LENGTH) {
558 // Single event for the whole lesson.
559 $event->name = $lesson->name;
560 add_event($event);
561 } else {
562 // Separate start and end events.
563 $event->timeduration = 0;
564 if ($lesson->available) {
565 $event->name = $lesson->name.' ('.get_string('lessonopens', 'lesson').')';
566 add_event($event);
567 unset($event->id); // So we can use the same object for the close event.
569 if ($lesson->deadline) {
570 $event->name = $lesson->name.' ('.get_string('lessoncloses', 'lesson').')';
571 $event->timestart = $lesson->deadline;
572 $event->eventtype = 'close';
573 add_event($event);
580 * Implementation of the function for printing the form elements that control
581 * whether the course reset functionality affects the lesson.
582 * @param $mform form passed by reference
584 function lesson_reset_course_form_definition(&$mform) {
585 $mform->addElement('header', 'lessonheader', get_string('modulenameplural', 'lesson'));
586 $mform->addElement('advcheckbox', 'reset_lesson', get_string('deleteallattempts','lesson'));
590 * Course reset form defaults.
592 function lesson_reset_course_form_defaults($course) {
593 return array('reset_lesson'=>1);
597 * Removes all grades from gradebook
598 * @param int $courseid
599 * @param string optional type
601 function lesson_reset_gradebook($courseid, $type='') {
602 global $CFG;
604 $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid
605 FROM {$CFG->prefix}lesson l, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
606 WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id AND l.course=$courseid";
608 if ($lessons = get_records_sql($sql)) {
609 foreach ($lessons as $lesson) {
610 lesson_grade_item_update($lesson, 'reset');
616 * Actual implementation of the rest coures functionality, delete all the
617 * lesson attempts for course $data->courseid.
618 * @param $data the data submitted from the reset course.
619 * @return array status array
621 function lesson_reset_userdata($data) {
622 global $CFG;
624 $componentstr = get_string('modulenameplural', 'lesson');
625 $status = array();
627 if (!empty($data->reset_lesson)) {
628 $lessonssql = "SELECT l.id
629 FROM {$CFG->prefix}lesson l
630 WHERE l.course={$data->courseid}";
633 delete_records_select('lesson_timer', "lessonid IN ($lessonssql)");
634 delete_records_select('lesson_high_scores', "lessonid IN ($lessonssql)");
635 delete_records_select('lesson_grades', "lessonid IN ($lessonssql)");
636 delete_records_select('lesson_attempts', "lessonid IN ($lessonssql)");
638 // remove all grades from gradebook
639 if (empty($data->reset_gradebook_grades)) {
640 lesson_reset_gradebook($data->courseid);
643 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'lesson'), 'error'=>false);
646 /// updating dates - shift may be negative too
647 if ($data->timeshift) {
648 shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid);
649 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
652 return $status;
656 * Returns all other caps used in module
658 function lesson_get_extra_capabilities() {
659 return array('moodle/site:accessallgroups');
663 * Tells if files in moddata are trusted and can be served without XSS protection.
664 * @return bool true if file can be submitted by teacher only (trusted), false otherwise
666 function lesson_is_moddata_trusted() {
667 return true;