MDL-24419 (5): Coding style cleanup
[moodle.git] / lib / conditionlib.php
blob9bcd4bbe5d27f6bf10d669f93678c285c931c1ef
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Used for tracking conditions that apply before activities are displayed
19 * to students ('conditional availability').
21 * @package core_condition
22 * @category condition
23 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
29 /**
30 * CONDITION_STUDENTVIEW_HIDE - The activity is not displayed to students at all when conditions aren't met.
32 define('CONDITION_STUDENTVIEW_HIDE', 0);
33 /**
34 * CONDITION_STUDENTVIEW_SHOW - The activity is displayed to students as a greyed-out name, with
35 * informational text that explains the conditions under which it will be available.
37 define('CONDITION_STUDENTVIEW_SHOW', 1);
39 /**
40 * CONDITION_MISSING_NOTHING - The $item variable is expected to contain all completion-related data
42 define('CONDITION_MISSING_NOTHING', 0);
43 /**
44 * CONDITION_MISSING_EXTRATABLE - The $item variable is expected to contain the fields from
45 * the relevant table (course_modules or course_sections) but not the _availability data
47 define('CONDITION_MISSING_EXTRATABLE', 1);
48 /**
49 * CONDITION_MISSING_EVERYTHING - The $item variable is expected to contain nothing except the ID
51 define('CONDITION_MISSING_EVERYTHING', 2);
53 require_once($CFG->libdir.'/completionlib.php');
55 /**
56 * Core class to handle conditional activites
58 * @package core_condition
59 * @category condition
60 * @copyright 2012 The Open University
61 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
63 class condition_info extends condition_info_base {
64 /**
65 * Constructs with course-module details.
67 * @global moodle_database $DB
68 * @uses CONDITION_MISSING_NOTHING
69 * @param object $cm Moodle course-module object. May have extra fields
70 * ->conditionsgrade, ->conditionscompletion which should come from
71 * get_fast_modinfo. Should have ->availablefrom, ->availableuntil,
72 * and ->showavailability, ->course; but the only required thing is ->id.
73 * @param int $expectingmissing Used to control whether or not a developer
74 * debugging message (performance warning) will be displayed if some of
75 * the above data is missing and needs to be retrieved; a
76 * CONDITION_MISSING_xx constant
77 * @param bool $loaddata If you need a 'write-only' object, set this value
78 * to false to prevent database access from constructor
80 public function __construct($cm, $expectingmissing = CONDITION_MISSING_NOTHING,
81 $loaddata=true) {
82 parent::__construct($cm, 'course_modules', 'coursemoduleid',
83 $expectingmissing, $loaddata);
86 /**
87 * Adds the extra availability conditions (if any) into the given
88 * course-module (or section) object.
90 * This function may be called statically (for the editing form) or
91 * dynamically.
93 * @param object $cm Moodle course-module data object
95 public static function fill_availability_conditions($cm) {
96 parent::fill_availability_conditions_inner($cm, 'course_modules', 'coursemoduleid');
99 /**
100 * Gets the course-module object with full necessary data to determine availability.
101 * @return object Course-module object with full data
102 * @throws coding_exception If data was not supplied when constructing object
104 public function get_full_course_module() {
105 return $this->get_full_item();
109 * Utility function called by modedit.php; updates the
110 * course_modules_availability table based on the module form data.
112 * @param object $cm Course-module with as much data as necessary, min id
113 * @param object $fromform Data from form
114 * @param bool $wipefirst If true, wipes existing conditions
116 public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
117 $ci = new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
118 parent::update_from_form($ci, $fromform, $wipefirst);
122 * Used in course/lib.php because we need to disable the completion JS if
123 * a completion value affects a conditional activity.
125 * @global stdClass $CONDITIONLIB_PRIVATE
126 * @param object $course Moodle course object
127 * @param object $item Moodle course-module
128 * @return bool True if this is used in a condition, false otherwise
130 public static function completion_value_used_as_condition($course, $cm) {
131 // Have we already worked out a list of required completion values
132 // for this course? If so just use that
133 global $CONDITIONLIB_PRIVATE, $DB;
134 if (!array_key_exists($course->id, $CONDITIONLIB_PRIVATE->usedincondition)) {
135 // We don't have data for this course, build it
136 $modinfo = get_fast_modinfo($course);
137 $CONDITIONLIB_PRIVATE->usedincondition[$course->id] = array();
139 // Activities
140 foreach ($modinfo->cms as $othercm) {
141 foreach ($othercm->conditionscompletion as $cmid => $expectedcompletion) {
142 $CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
146 // Sections
147 foreach ($modinfo->get_section_info_all() as $section) {
148 foreach ($section->conditionscompletion as $cmid => $expectedcompletion) {
149 $CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
153 return array_key_exists($cm->id, $CONDITIONLIB_PRIVATE->usedincondition[$course->id]);
159 * Handles conditional access to sections.
161 * @package core_condition
162 * @category condition
163 * @copyright 2012 The Open University
164 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
166 class condition_info_section extends condition_info_base {
168 * Constructs with course-module details.
170 * @global moodle_database $DB
171 * @uses CONDITION_MISSING_NOTHING
172 * @param object $section Moodle section object. May have extra fields
173 * ->conditionsgrade, ->conditionscompletion. Should have ->availablefrom,
174 * ->availableuntil, and ->showavailability, ->course; but the only
175 * required thing is ->id.
176 * @param int $expectingmissing Used to control whether or not a developer
177 * debugging message (performance warning) will be displayed if some of
178 * the above data is missing and needs to be retrieved; a
179 * CONDITION_MISSING_xx constant
180 * @param bool $loaddata If you need a 'write-only' object, set this value
181 * to false to prevent database access from constructor
183 public function __construct($section, $expectingmissing = CONDITION_MISSING_NOTHING,
184 $loaddata=true) {
185 parent::__construct($section, 'course_sections', 'coursesectionid',
186 $expectingmissing, $loaddata);
190 * Adds the extra availability conditions (if any) into the given
191 * course-module (or section) object.
193 * This function may be called statically (for the editing form) or
194 * dynamically.
196 * @param object $section Moodle section data object
198 public static function fill_availability_conditions($section) {
199 parent::fill_availability_conditions_inner($section, 'course_sections', 'coursesectionid');
203 * Gets the section object with full necessary data to determine availability.
204 * @return object Section object with full data
205 * @throws coding_exception If data was not supplied when constructing object
207 public function get_full_section() {
208 return $this->get_full_item();
212 * Gets list of required fields from main table.
213 * @return array Array of field names
215 protected function get_main_table_fields() {
216 return array_merge(parent::get_main_table_fields(), array('groupingid'));
220 * Determines whether this particular section is currently available
221 * according to these criteria.
223 * - This does not include the 'visible' setting (i.e. this might return
224 * true even if visible is false); visible is handled independently.
225 * - This does not take account of the viewhiddenactivities capability.
226 * That should apply later.
228 * @global moodle_database $DB
229 * @global stdclass $USER
230 * @param string $information If the item has availability restrictions,
231 * a string that describes the conditions will be stored in this variable;
232 * if this variable is set blank, that means don't display anything
233 * @param bool $grabthelot Performance hint: if true, caches information
234 * required for all course-modules, to make the front page and similar
235 * pages work more quickly (works only for current user)
236 * @param int $userid If set, specifies a different user ID to check availability for
237 * @param object $modinfo Usually leave as null for default. Specify when
238 * calling recursively from inside get_fast_modinfo. The value supplied
239 * here must include list of all CMs with 'id' and 'name'
240 * @return bool True if this item is available to the user, false otherwise
242 public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
243 global $DB, $USER, $CONDITIONLIB_PRIVATE;
245 $available = parent::is_available($information, $grabthelot, $userid, $modinfo);
247 // test if user is enrolled to a grouping which has access to the section
248 if (!empty($this->item->groupingid)) {
249 // Get real user id
250 if (!$userid) {
251 $userid = $USER->id;
253 $context = context_course::instance($this->item->course);
255 if ($userid != $USER->id) {
256 // We are requesting for a non-current user so check it individually
257 // (no cache). Do grouping check first, it's probably faster
258 // than the capability check
259 $gotit = $DB->record_exists_sql('
260 SELECT
262 FROM
263 {groupings} g
264 JOIN {groupings_groups} gg ON g.id = gg.groupingid
265 JOIN {groups_members} gm ON gg.groupid = gm.groupid
266 WHERE
267 g.id = ? AND gm.userid = ?',
268 array($this->item->groupingid, $userid));
269 if (!$gotit && !has_capability('moodle/site:accessallgroups', $context, $userid)) {
270 $available = false;
271 $information .= get_string('groupingnoaccess', 'condition');
273 } else {
274 // Request is for current user - use cache
275 if( !array_key_exists($this->item->course, $CONDITIONLIB_PRIVATE->groupingscache)) {
276 if (has_capability('moodle/site:accessallgroups', $context)) {
277 $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = true;
278 } else {
279 $groupings = $DB->get_records_sql('
280 SELECT
281 g.id as gid
282 FROM
283 {groupings} g
284 JOIN {groupings_groups} gg ON g.id = gg.groupingid
285 JOIN {groups_members} gm ON gg.groupid = gm.groupid
286 WHERE
287 g.courseid = ? AND gm.userid = ?',
288 array($this->item->course, $userid));
289 $list = array();
290 foreach ($groupings as $grouping) {
291 $list[$grouping->gid] = true;
293 $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = $list;
297 $usergroupings = $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course];
298 if ($usergroupings !== true && !array_key_exists($this->item->groupingid, $usergroupings)) {
299 $available = false;
300 $information .= get_string('groupingnoaccess', 'condition');
305 $information = trim($information);
306 return $available;
310 * Utility function called by modedit.php; updates the
311 * course_modules_availability table based on the module form data.
313 * @param object $section Section object, must at minimum contain id
314 * @param object $fromform Data from form
315 * @param bool $wipefirst If true, wipes existing conditions
317 public static function update_section_from_form($section, $fromform, $wipefirst=true) {
318 $ci = new condition_info_section($section, CONDITION_MISSING_EVERYTHING);
319 parent::update_from_form($ci, $fromform, $wipefirst);
325 * Base class to handle conditional items of some kind (currently either
326 * course_modules or sections; they must have a corresponding _availability
327 * table).
329 * @package core_condition
330 * @category condition
331 * @copyright 2012 The Open University
332 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
334 abstract class condition_info_base {
335 /** @var object, bool, string, string, array */
336 protected $item, $gotdata, $availtable, $idfieldname, $usergroupings;
339 * Constructs with item details.
341 * @global moodle_database $DB
342 * @uses CONDITION_MISSING_NOTHING
343 * @uses CONDITION_MISSING_EVERYTHING
344 * @uses CONDITION_MISSING_EXTRATABLE
345 * @uses DEBUG_DEVELOPER
346 * @param object $item Object representing some kind of item (cm or section).
347 * May have extra fields ->conditionsgrade, ->conditionscompletion.
348 * Should have ->availablefrom, ->availableuntil, and ->showavailability,
349 * ->course; but the only required thing is ->id.
350 * @param string $tableprefix Prefix for table used to store availability
351 * data, e.g. 'course_modules' if we are going to look at
352 * course_modules_availability.
353 * @param string $idfield Within this table, name of field used as item id
354 * (e.g. 'coursemoduleid')
355 * @param int $expectingmissing Used to control whether or not a developer
356 * debugging message (performance warning) will be displayed if some of
357 * the above data is missing and needs to be retrieved; a
358 * CONDITION_MISSING_xx constant
359 * @param bool $loaddata If you need a 'write-only' object, set this value
360 * to false to prevent database access from constructor
361 * @return condition_info Object which can retrieve information about the
362 * activity
364 public function __construct($item, $tableprefix, $idfield, $expectingmissing, $loaddata) {
365 global $DB;
367 // Check ID as otherwise we can't do the other queries
368 if (empty($item->id)) {
369 throw new coding_exception('Invalid parameters; item ID not included');
372 // DB table to store availability conditions
373 $this->availtable = $tableprefix . '_availability';
375 // Name of module/section ID field in DB
376 $this->idfieldname = $idfield;
378 // If not loading data, don't do anything else
379 if (!$loaddata) {
380 $this->item = (object)array('id' => $item->id);
381 $this->gotdata = false;
382 return;
385 // Missing basic data from course_modules
386 $basicfields = $this->get_main_table_fields();
387 $missingbasicfields = false;
388 foreach ($basicfields as $field) {
389 if (!isset($item->{$field})) {
390 $missingbasicfields = true;
391 break;
394 if ($missingbasicfields) {
395 if ($expectingmissing<CONDITION_MISSING_EVERYTHING) {
396 debugging('Performance warning: condition_info constructor is ' .
397 'faster if you pass in $item with at least basic fields ' .
398 'from its table. '.
399 '[This warning can be disabled, see phpdoc.]',
400 DEBUG_DEVELOPER);
402 $item = $DB->get_record($tableprefix, array('id' => $item->id),
403 implode(',', $basicfields), MUST_EXIST);
406 $this->item = clone($item);
407 $this->gotdata = true;
409 // Missing extra data
410 if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
411 if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
412 debugging('Performance warning: condition_info constructor is ' .
413 'faster if you pass in a $item from get_fast_modinfo or ' .
414 'the equivalent for sections. ' .
415 '[This warning can be disabled, see phpdoc.]',
416 DEBUG_DEVELOPER);
419 $this->fill_availability_conditions($this->item);
424 * Gets list of required fields from main table.
426 * @return array Array of field names
428 protected function get_main_table_fields() {
429 return array('id', 'course', 'availablefrom', 'availableuntil', 'showavailability');
433 * Fills availability conditions into the item object, if they are missing,
434 * otherwise does nothing. Called by subclass fill_availability_conditions.
435 * @param object $item Item object
436 * @param string $tableprefix Prefix of name for _availability table e.g. 'course_modules'
437 * @param string $idfield Name of field that contains id e.g. 'coursemoduleid'
438 * @throws coding_exception If item object doesn't have id field
440 protected static function fill_availability_conditions_inner($item, $tableprefix, $idfield) {
441 global $DB, $CFG;
442 if (empty($item->id)) {
443 throw new coding_exception('Invalid parameters; item ID not included');
446 // Does nothing if the variables are already present
447 if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
448 $item->conditionsgrade = array();
449 $item->conditionscompletion = array();
451 $conditions = $DB->get_records_sql('
452 SELECT
453 a.id AS aid, gi.*, a.sourcecmid, a.requiredcompletion, a.gradeitemid,
454 a.grademin as conditiongrademin, a.grademax as conditiongrademax
455 FROM
456 {' . $tableprefix . '_availability} a
457 LEFT JOIN {grade_items} gi ON gi.id = a.gradeitemid
458 WHERE ' . $idfield . ' = ?', array($item->id));
459 foreach ($conditions as $condition) {
460 if (!is_null($condition->sourcecmid)) {
461 $item->conditionscompletion[$condition->sourcecmid] =
462 $condition->requiredcompletion;
463 } else {
464 $minmax = new stdClass;
465 $minmax->min = $condition->conditiongrademin;
466 $minmax->max = $condition->conditiongrademax;
467 $minmax->name = self::get_grade_name($condition);
468 $item->conditionsgrade[$condition->gradeitemid] = $minmax;
475 * Obtains the name of a grade item.
477 * @global object $CFG
478 * @param object $gradeitemobj Object from get_record on grade_items table,
479 * (can be empty if you want to just get !missing)
480 * @return string Name of item of !missing if it didn't exist
482 private static function get_grade_name($gradeitemobj) {
483 global $CFG;
484 if (isset($gradeitemobj->id)) {
485 require_once($CFG->libdir . '/gradelib.php');
486 $item = new grade_item;
487 grade_object::set_properties($item, $gradeitemobj);
488 return $item->get_name();
489 } else {
490 return '!missing'; // Ooops, missing grade
495 * Gets the item object with full necessary data to determine availability.
496 * @return object Item object with full data
497 * @throws coding_exception If data was not supplied when constructing object
499 protected function get_full_item() {
500 $this->require_data();
501 return $this->item;
505 * Adds to the database a condition based on completion of another module.
507 * @global moodle_database $DB
508 * @param int $cmid ID of other module
509 * @param int $requiredcompletion COMPLETION_xx constant
511 public function add_completion_condition($cmid, $requiredcompletion) {
512 global $DB;
513 // Add to DB
514 $DB->insert_record($this->availtable, (object)array(
515 $this->idfieldname => $this->item->id,
516 'sourcecmid' => $cmid, 'requiredcompletion' => $requiredcompletion),
517 false);
519 // Store in memory too
520 $this->item->conditionscompletion[$cmid] = $requiredcompletion;
524 * Adds to the database a condition based on the value of a grade item.
526 * @global moodle_database $DB
527 * @param int $gradeitemid ID of grade item
528 * @param float $min Minimum grade (>=), up to 5 decimal points, or null if none
529 * @param float $max Maximum grade (<), up to 5 decimal points, or null if none
530 * @param bool $updateinmemory If true, updates data in memory; otherwise,
531 * memory version may be out of date (this has performance consequences,
532 * so don't do it unless it really needs updating)
534 public function add_grade_condition($gradeitemid, $min, $max, $updateinmemory=false) {
535 global $DB;
536 // Normalise nulls
537 if ($min==='') {
538 $min = null;
540 if ($max==='') {
541 $max = null;
543 // Add to DB
544 $DB->insert_record($this->availtable, (object)array(
545 $this->idfieldname => $this->item->id,
546 'gradeitemid' => $gradeitemid, 'grademin' => $min, 'grademax' => $max),
547 false);
549 // Store in memory too
550 if ($updateinmemory) {
551 $this->item->conditionsgrade[$gradeitemid] = (object) array(
552 'min' => $min, 'max' => $max);
553 $this->item->conditionsgrade[$gradeitemid]->name = self::get_grade_name(
554 $DB->get_record('grade_items', array('id'=>$gradeitemid)));
559 * Erases from the database all conditions for this activity.
561 * @global moodle_database $DB
563 public function wipe_conditions() {
564 // Wipe from DB
565 global $DB;
566 $DB->delete_records($this->availtable, array($this->idfieldname => $this->item->id));
568 // And from memory
569 $this->item->conditionsgrade = array();
570 $this->item->conditionscompletion = array();
574 * Obtains a string describing all availability restrictions (even if
575 * they do not apply any more).
577 * @global stdClass $COURSE
578 * @global moodle_database $DB
579 * @param object $modinfo Usually leave as null for default. Specify when
580 * calling recursively from inside get_fast_modinfo. The value supplied
581 * here must include list of all CMs with 'id' and 'name'
582 * @return string Information string (for admin) about all restrictions on
583 * this item
585 public function get_full_information($modinfo=null) {
586 global $COURSE, $DB;
587 $this->require_data();
589 $information = '';
591 // Completion conditions
592 if (count($this->item->conditionscompletion) > 0) {
593 if ($this->item->course == $COURSE->id) {
594 $course = $COURSE;
595 } else {
596 $course = $DB->get_record('course', array('id' => $this->item->course),
597 'id, enablecompletion, modinfo', MUST_EXIST);
599 foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
600 if (!$modinfo) {
601 $modinfo = get_fast_modinfo($course);
603 if (empty($modinfo->cms[$cmid])) {
604 continue;
606 $information .= get_string(
607 'requires_completion_' . $expectedcompletion,
608 'condition', $modinfo->cms[$cmid]->name) . ' ';
612 // Grade conditions
613 if (count($this->item->conditionsgrade) > 0) {
614 foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
615 // String depends on type of requirement. We are coy about
616 // the actual numbers, in case grades aren't released to
617 // students.
618 if (is_null($minmax->min) && is_null($minmax->max)) {
619 $string = 'any';
620 } else if (is_null($minmax->max)) {
621 $string = 'min';
622 } else if (is_null($minmax->min)) {
623 $string = 'max';
624 } else {
625 $string = 'range';
627 $information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
631 // The date logic is complicated. The intention of this logic is:
632 // 1) display date without time where possible (whenever the date is
633 // midnight)
634 // 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as
635 // 'until the 13th' (experience at the OU showed that students are
636 // likely to interpret 'until <date>' as 'until the end of <date>').
637 // 3) This behaviour becomes confusing for 'same-day' dates where there
638 // are some exceptions.
639 // Users in different time zones will typically not get the 'abbreviated'
640 // behaviour but it should work OK for them aside from that.
642 // The following cases are possible:
643 // a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
644 // b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
645 // c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
646 // d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct)
647 // e) On 14 Oct (midnight 14 Oct, midnight 15 Oct)
648 // f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day)
649 // g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day)
650 // h) From 13:05 on 14 Oct (exact)
651 // i) From 14 Oct (midnight)
652 // j) Until 13:05 on 14 Oct (exact)
653 // k) Until 14 Oct (midnight 15 Oct)
655 // Check if start and end dates are 'midnights', if so we show in short form
656 $shortfrom = self::is_midnight($this->item->availablefrom);
657 $shortuntil = self::is_midnight($this->item->availableuntil);
659 // For some checks and for display, we need the previous day for the 'until'
660 // value, if we are going to display it in short form
661 if ($this->item->availableuntil) {
662 $daybeforeuntil = strtotime('-1 day', usergetmidnight($this->item->availableuntil));
665 // Special case for if one but not both are exact and they are within a day
666 if ($this->item->availablefrom && $this->item->availableuntil &&
667 $shortfrom != $shortuntil && $daybeforeuntil < $this->item->availablefrom) {
668 // Don't use abbreviated version (see examples f, g above)
669 $shortfrom = false;
670 $shortuntil = false;
673 // When showing short end date, the display time is the 'day before' one
674 $displayuntil = $shortuntil ? $daybeforeuntil : $this->item->availableuntil;
676 if ($this->item->availablefrom && $this->item->availableuntil) {
677 if ($shortfrom && $shortuntil && $daybeforeuntil == $this->item->availablefrom) {
678 $information .= get_string('requires_date_both_single_day', 'condition',
679 self::show_time($this->item->availablefrom, true));
680 } else {
681 $information .= get_string('requires_date_both', 'condition', (object)array(
682 'from' => self::show_time($this->item->availablefrom, $shortfrom),
683 'until' => self::show_time($displayuntil, $shortuntil)));
685 } else if ($this->item->availablefrom) {
686 $information .= get_string('requires_date', 'condition',
687 self::show_time($this->item->availablefrom, $shortfrom));
688 } else if ($this->item->availableuntil) {
689 $information .= get_string('requires_date_before', 'condition',
690 self::show_time($displayuntil, $shortuntil));
693 $information = trim($information);
694 return $information;
698 * Checks whether a given time refers exactly to midnight (in current user
699 * timezone).
701 * @param int $time Time
702 * @return bool True if time refers to midnight, false if it's some other
703 * time or if it is set to zero
705 private static function is_midnight($time) {
706 return $time && usergetmidnight($time) == $time;
710 * Determines whether this particular item is currently available
711 * according to these criteria.
713 * - This does not include the 'visible' setting (i.e. this might return
714 * true even if visible is false); visible is handled independently.
715 * - This does not take account of the viewhiddenactivities capability.
716 * That should apply later.
718 * @global stdClass $COURSE
719 * @global moodle_database $DB
720 * @uses COMPLETION_COMPLETE
721 * @uses COMPLETION_COMPLETE_FAIL
722 * @uses COMPLETION_COMPLETE_PASS
723 * @param string $information If the item has availability restrictions,
724 * a string that describes the conditions will be stored in this variable;
725 * if this variable is set blank, that means don't display anything
726 * @param bool $grabthelot Performance hint: if true, caches information
727 * required for all course-modules, to make the front page and similar
728 * pages work more quickly (works only for current user)
729 * @param int $userid If set, specifies a different user ID to check availability for
730 * @param object $modinfo Usually leave as null for default. Specify when
731 * calling recursively from inside get_fast_modinfo. The value supplied
732 * here must include list of all CMs with 'id' and 'name'
733 * @return bool True if this item is available to the user, false otherwise
735 public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
736 global $COURSE, $DB;
737 $this->require_data();
739 $available = true;
740 $information = '';
742 // Check each completion condition
743 if (count($this->item->conditionscompletion) > 0) {
744 if ($this->item->course == $COURSE->id) {
745 $course = $COURSE;
746 } else {
747 $course = $DB->get_record('course', array('id' => $this->item->course),
748 'id, enablecompletion, modinfo', MUST_EXIST);
751 $completion = new completion_info($course);
752 foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
753 // If this depends on a deleted module, handle that situation
754 // gracefully.
755 if (!$modinfo) {
756 $modinfo = get_fast_modinfo($course);
758 if (empty($modinfo->cms[$cmid])) {
759 global $PAGE, $UNITTEST;
760 if (!empty($UNITTEST) || (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-')===0)) {
761 debugging("Warning: activity {$this->cm->id} '{$this->cm->name}' has condition " .
762 "on deleted activity $cmid (to get rid of this message, edit the named activity)");
764 continue;
767 // The completion system caches its own data
768 $completiondata = $completion->get_data((object)array('id' => $cmid),
769 $grabthelot, $userid, $modinfo);
771 $thisisok = true;
772 if ($expectedcompletion==COMPLETION_COMPLETE) {
773 // 'Complete' also allows the pass, fail states
774 switch ($completiondata->completionstate) {
775 case COMPLETION_COMPLETE:
776 case COMPLETION_COMPLETE_FAIL:
777 case COMPLETION_COMPLETE_PASS:
778 break;
779 default:
780 $thisisok = false;
782 } else {
783 // Other values require exact match
784 if ($completiondata->completionstate!=$expectedcompletion) {
785 $thisisok = false;
788 if (!$thisisok) {
789 $available = false;
790 $information .= get_string(
791 'requires_completion_' . $expectedcompletion,
792 'condition', $modinfo->cms[$cmid]->name) . ' ';
797 // Check each grade condition
798 if (count($this->item->conditionsgrade)>0) {
799 foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
800 $score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid);
801 if ($score===false ||
802 (!is_null($minmax->min) && $score<$minmax->min) ||
803 (!is_null($minmax->max) && $score>=$minmax->max)) {
804 // Grade fail
805 $available = false;
806 // String depends on type of requirement. We are coy about
807 // the actual numbers, in case grades aren't released to
808 // students.
809 if (is_null($minmax->min) && is_null($minmax->max)) {
810 $string = 'any';
811 } else if (is_null($minmax->max)) {
812 $string = 'min';
813 } else if (is_null($minmax->min)) {
814 $string = 'max';
815 } else {
816 $string = 'range';
818 $information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' ';
823 // Test dates
824 if ($this->item->availablefrom) {
825 if (time() < $this->item->availablefrom) {
826 $available = false;
828 $information .= get_string('requires_date', 'condition',
829 self::show_time($this->item->availablefrom,
830 self::is_midnight($this->item->availablefrom)));
834 if ($this->item->availableuntil) {
835 if (time() >= $this->item->availableuntil) {
836 $available = false;
837 // But we don't display any information about this case. This is
838 // because the only reason to set a 'disappear' date is usually
839 // to get rid of outdated information/clutter in which case there
840 // is no point in showing it...
842 // Note it would be nice if we could make it so that the 'until'
843 // date appears below the item while the item is still accessible,
844 // unfortunately this is not possible in the current system. Maybe
845 // later, or if somebody else wants to add it.
849 $information = trim($information);
850 return $available;
854 * Shows a time either as a date or a full date and time, according to
855 * user's timezone.
857 * @param int $time Time
858 * @param bool $dateonly If true, uses date only
859 * @return string Date
861 private function show_time($time, $dateonly) {
862 return userdate($time,
863 get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
867 * Checks whether availability information should be shown to normal users.
869 * @return bool True if information about availability should be shown to
870 * normal users
871 * @throws coding_exception If data wasn't loaded
873 public function show_availability() {
874 $this->require_data();
875 return $this->item->showavailability;
879 * Internal function cheks that data was loaded.
881 * @throws coding_exception If data wasn't loaded
883 private function require_data() {
884 if (!$this->gotdata) {
885 throw new coding_exception('Error: cannot call when info was ' .
886 'constructed without data');
891 * Obtains a grade score. Note that this score should not be displayed to
892 * the user, because gradebook rules might prohibit that. It may be a
893 * non-final score subject to adjustment later.
895 * @global stdClass $USER
896 * @global moodle_database $DB
897 * @global stdClass $SESSION
898 * @param int $gradeitemid Grade item ID we're interested in
899 * @param bool $grabthelot If true, grabs all scores for current user on
900 * this course, so that later ones come from cache
901 * @param int $userid Set if requesting grade for a different user (does
902 * not use cache)
903 * @return float Grade score as a percentage in range 0-100 (e.g. 100.0
904 * or 37.21), or false if user does not have a grade yet
906 private function get_cached_grade_score($gradeitemid, $grabthelot=false, $userid=0) {
907 global $USER, $DB, $SESSION;
908 if ($userid==0 || $userid==$USER->id) {
909 // For current user, go via cache in session
910 if (empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
911 $SESSION->gradescorecache = array();
912 $SESSION->gradescorecacheuserid = $USER->id;
914 if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
915 if ($grabthelot) {
916 // Get all grades for the current course
917 $rs = $DB->get_recordset_sql('
918 SELECT
919 gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
920 FROM
921 {grade_items} gi
922 LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
923 WHERE
924 gi.courseid = ?', array($USER->id, $this->item->course));
925 foreach ($rs as $record) {
926 $SESSION->gradescorecache[$record->id] =
927 is_null($record->finalgrade)
928 // No grade = false
929 ? false
930 // Otherwise convert grade to percentage
931 : (($record->finalgrade - $record->rawgrademin) * 100) /
932 ($record->rawgrademax - $record->rawgrademin);
935 $rs->close();
936 // And if it's still not set, well it doesn't exist (eg
937 // maybe the user set it as a condition, then deleted the
938 // grade item) so we call it false
939 if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
940 $SESSION->gradescorecache[$gradeitemid] = false;
942 } else {
943 // Just get current grade
944 $record = $DB->get_record('grade_grades', array(
945 'userid'=>$USER->id, 'itemid'=>$gradeitemid));
946 if ($record && !is_null($record->finalgrade)) {
947 $score = (($record->finalgrade - $record->rawgrademin) * 100) /
948 ($record->rawgrademax - $record->rawgrademin);
949 } else {
950 // Treat the case where row exists but is null, same as
951 // case where row doesn't exist
952 $score = false;
954 $SESSION->gradescorecache[$gradeitemid]=$score;
957 return $SESSION->gradescorecache[$gradeitemid];
958 } else {
959 // Not the current user, so request the score individually
960 $record = $DB->get_record('grade_grades', array(
961 'userid'=>$userid, 'itemid'=>$gradeitemid));
962 if ($record && !is_null($record->finalgrade)) {
963 $score = (($record->finalgrade - $record->rawgrademin) * 100) /
964 ($record->rawgrademax - $record->rawgrademin);
965 } else {
966 // Treat the case where row exists but is null, same as
967 // case where row doesn't exist
968 $score = false;
970 return $score;
975 * For testing only. Wipes information cached in user session.
977 * @global stdClass $SESSION
979 static function wipe_session_cache() {
980 global $SESSION;
981 unset($SESSION->gradescorecache);
982 unset($SESSION->gradescorecacheuserid);
986 * Initialises the global cache
987 * @global stdClass $CONDITIONLIB_PRIVATE
989 public static function init_global_cache() {
990 global $CONDITIONLIB_PRIVATE;
991 $CONDITIONLIB_PRIVATE = new stdClass;
992 $CONDITIONLIB_PRIVATE->usedincondition = array();
993 $CONDITIONLIB_PRIVATE->groupingscache = array();
997 * Utility function that resets grade/completion conditions in table based
998 * in data from editing form.
1000 * @param condition_info_base $ci Condition info
1001 * @param object $fromform Data from form
1002 * @param bool $wipefirst If true, wipes existing conditions
1004 protected static function update_from_form(condition_info_base $ci, $fromform, $wipefirst) {
1005 if ($wipefirst) {
1006 $ci->wipe_conditions();
1008 foreach ($fromform->conditiongradegroup as $record) {
1009 if($record['conditiongradeitemid']) {
1010 $ci->add_grade_condition($record['conditiongradeitemid'],
1011 unformat_float($record['conditiongrademin']), unformat_float($record['conditiongrademax']));
1014 if(isset ($fromform->conditioncompletiongroup)) {
1015 foreach($fromform->conditioncompletiongroup as $record) {
1016 if($record['conditionsourcecmid']) {
1017 $ci->add_completion_condition($record['conditionsourcecmid'],
1018 $record['conditionrequiredcompletion']);
1025 condition_info::init_global_cache();