MDL-35089 conditionals: detected one place missing sectioncache
[moodle.git] / lib / conditionlib.php
CommitLineData
82bd6a5e 1<?php
117bd748
PS
2// This file is part of Moodle - http://moodle.org/
3//
3564771d 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.
117bd748 13//
3564771d 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/>.
16
17/**
18 * Used for tracking conditions that apply before activities are displayed
19 * to students ('conditional availability').
117bd748 20 *
9ccadad3
AKA
21 * @package core_condition
22 * @category condition
78bfb562
PS
23 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3564771d 25 */
82bd6a5e 26
78bfb562
PS
27defined('MOODLE_INTERNAL') || die();
28
47ee3c55
EL
29/**
30 * CONDITION_STUDENTVIEW_HIDE - The activity is not displayed to students at all when conditions aren't met.
9ccadad3 31 */
ce4dfd27 32define('CONDITION_STUDENTVIEW_HIDE', 0);
47ee3c55
EL
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.
9ccadad3 36 */
ce4dfd27 37define('CONDITION_STUDENTVIEW_SHOW', 1);
82bd6a5e 38
47ee3c55 39/**
ce4dfd27 40 * CONDITION_MISSING_NOTHING - The $item variable is expected to contain all completion-related data
9ccadad3 41 */
ce4dfd27 42define('CONDITION_MISSING_NOTHING', 0);
47ee3c55 43/**
ce4dfd27 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
9ccadad3 46 */
ce4dfd27 47define('CONDITION_MISSING_EXTRATABLE', 1);
47ee3c55 48/**
ce4dfd27 49 * CONDITION_MISSING_EVERYTHING - The $item variable is expected to contain nothing except the ID
9ccadad3 50 */
ce4dfd27 51define('CONDITION_MISSING_EVERYTHING', 2);
82bd6a5e 52
516c5eca
PS
53require_once($CFG->libdir.'/completionlib.php');
54
3564771d 55/**
9ccadad3 56 * Core class to handle conditional activites
47ee3c55 57 *
9ccadad3
AKA
58 * @package core_condition
59 * @category condition
ce4dfd27 60 * @copyright 2012 The Open University
9ccadad3 61 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3564771d 62 */
ce4dfd27 63class condition_info extends condition_info_base {
82bd6a5e 64 /**
65 * Constructs with course-module details.
66 *
9ccadad3 67 * @global moodle_database $DB
3564771d 68 * @uses CONDITION_MISSING_NOTHING
82bd6a5e 69 * @param object $cm Moodle course-module object. May have extra fields
117bd748
PS
70 * ->conditionsgrade, ->conditionscompletion which should come from
71 * get_fast_modinfo. Should have ->availablefrom, ->availableuntil,
540f89d7 72 * and ->showavailability, ->course, ->visible; but the only required
73 * thing is ->id.
82bd6a5e 74 * @param int $expectingmissing Used to control whether or not a developer
117bd748
PS
75 * debugging message (performance warning) will be displayed if some of
76 * the above data is missing and needs to be retrieved; a
82bd6a5e 77 * CONDITION_MISSING_xx constant
78 * @param bool $loaddata If you need a 'write-only' object, set this value
79 * to false to prevent database access from constructor
ce4dfd27 80 */
81 public function __construct($cm, $expectingmissing = CONDITION_MISSING_NOTHING,
82 $loaddata=true) {
83 parent::__construct($cm, 'course_modules', 'coursemoduleid',
84 $expectingmissing, $loaddata);
85 }
86
87 /**
88 * Adds the extra availability conditions (if any) into the given
89 * course-module (or section) object.
90 *
91 * This function may be called statically (for the editing form) or
92 * dynamically.
93 *
94 * @param object $cm Moodle course-module data object
95 */
96 public static function fill_availability_conditions($cm) {
97 parent::fill_availability_conditions_inner($cm, 'course_modules', 'coursemoduleid');
98 }
99
100 /**
101 * Gets the course-module object with full necessary data to determine availability.
102 * @return object Course-module object with full data
103 * @throws coding_exception If data was not supplied when constructing object
94dc3c7d 104 */
ce4dfd27 105 public function get_full_course_module() {
106 return $this->get_full_item();
107 }
108
109 /**
110 * Utility function called by modedit.php; updates the
111 * course_modules_availability table based on the module form data.
112 *
113 * @param object $cm Course-module with as much data as necessary, min id
114 * @param object $fromform Data from form
115 * @param bool $wipefirst If true, wipes existing conditions
116 */
117 public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
118 $ci = new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
119 parent::update_from_form($ci, $fromform, $wipefirst);
120 }
121
94dc3c7d 122 /**
ce4dfd27 123 * Used in course/lib.php because we need to disable the completion JS if
124 * a completion value affects a conditional activity.
125 *
126 * @global stdClass $CONDITIONLIB_PRIVATE
127 * @param object $course Moodle course object
128 * @param object $item Moodle course-module
129 * @return bool True if this is used in a condition, false otherwise
130 */
131 public static function completion_value_used_as_condition($course, $cm) {
132 // Have we already worked out a list of required completion values
133 // for this course? If so just use that
134 global $CONDITIONLIB_PRIVATE, $DB;
135 if (!array_key_exists($course->id, $CONDITIONLIB_PRIVATE->usedincondition)) {
136 // We don't have data for this course, build it
137 $modinfo = get_fast_modinfo($course);
138 $CONDITIONLIB_PRIVATE->usedincondition[$course->id] = array();
139
140 // Activities
141 foreach ($modinfo->cms as $othercm) {
94dc3c7d 142 foreach ($othercm->conditionscompletion as $cmid => $expectedcompletion) {
ce4dfd27 143 $CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
144 }
145 }
146
147 // Sections
148 foreach ($modinfo->get_section_info_all() as $section) {
149 foreach ($section->conditionscompletion as $cmid => $expectedcompletion) {
150 $CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
151 }
152 }
153 }
154 return array_key_exists($cm->id, $CONDITIONLIB_PRIVATE->usedincondition[$course->id]);
155 }
156}
157
158
159/**
160 * Handles conditional access to sections.
161 *
162 * @package core_condition
163 * @category condition
164 * @copyright 2012 The Open University
165 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
166 */
167class condition_info_section extends condition_info_base {
168 /**
169 * Constructs with course-module details.
170 *
171 * @global moodle_database $DB
172 * @uses CONDITION_MISSING_NOTHING
173 * @param object $section Moodle section object. May have extra fields
174 * ->conditionsgrade, ->conditionscompletion. Should have ->availablefrom,
175 * ->availableuntil, and ->showavailability, ->course; but the only
176 * required thing is ->id.
177 * @param int $expectingmissing Used to control whether or not a developer
178 * debugging message (performance warning) will be displayed if some of
179 * the above data is missing and needs to be retrieved; a
180 * CONDITION_MISSING_xx constant
181 * @param bool $loaddata If you need a 'write-only' object, set this value
182 * to false to prevent database access from constructor
183 */
184 public function __construct($section, $expectingmissing = CONDITION_MISSING_NOTHING,
185 $loaddata=true) {
186 parent::__construct($section, 'course_sections', 'coursesectionid',
187 $expectingmissing, $loaddata);
188 }
189
190 /**
191 * Adds the extra availability conditions (if any) into the given
192 * course-module (or section) object.
193 *
194 * This function may be called statically (for the editing form) or
195 * dynamically.
196 *
197 * @param object $section Moodle section data object
198 */
199 public static function fill_availability_conditions($section) {
200 parent::fill_availability_conditions_inner($section, 'course_sections', 'coursesectionid');
201 }
202
203 /**
204 * Gets the section object with full necessary data to determine availability.
205 * @return object Section object with full data
206 * @throws coding_exception If data was not supplied when constructing object
94dc3c7d 207 */
ce4dfd27 208 public function get_full_section() {
209 return $this->get_full_item();
210 }
211
212 /**
213 * Gets list of required fields from main table.
214 * @return array Array of field names
215 */
216 protected function get_main_table_fields() {
217 return array_merge(parent::get_main_table_fields(), array('groupingid'));
218 }
219
220 /**
221 * Determines whether this particular section is currently available
222 * according to these criteria.
223 *
224 * - This does not include the 'visible' setting (i.e. this might return
225 * true even if visible is false); visible is handled independently.
226 * - This does not take account of the viewhiddenactivities capability.
227 * That should apply later.
228 *
229 * @global moodle_database $DB
230 * @global stdclass $USER
231 * @param string $information If the item has availability restrictions,
232 * a string that describes the conditions will be stored in this variable;
233 * if this variable is set blank, that means don't display anything
234 * @param bool $grabthelot Performance hint: if true, caches information
235 * required for all course-modules, to make the front page and similar
236 * pages work more quickly (works only for current user)
237 * @param int $userid If set, specifies a different user ID to check availability for
238 * @param object $modinfo Usually leave as null for default. Specify when
239 * calling recursively from inside get_fast_modinfo. The value supplied
240 * here must include list of all CMs with 'id' and 'name'
241 * @return bool True if this item is available to the user, false otherwise
242 */
243 public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
244 global $DB, $USER, $CONDITIONLIB_PRIVATE;
245
246 $available = parent::is_available($information, $grabthelot, $userid, $modinfo);
247
248 // test if user is enrolled to a grouping which has access to the section
249 if (!empty($this->item->groupingid)) {
250 // Get real user id
251 if (!$userid) {
252 $userid = $USER->id;
253 }
254 $context = context_course::instance($this->item->course);
255
256 if ($userid != $USER->id) {
257 // We are requesting for a non-current user so check it individually
258 // (no cache). Do grouping check first, it's probably faster
259 // than the capability check
260 $gotit = $DB->record_exists_sql('
261 SELECT
262 1
263 FROM
264 {groupings} g
265 JOIN {groupings_groups} gg ON g.id = gg.groupingid
266 JOIN {groups_members} gm ON gg.groupid = gm.groupid
267 WHERE
268 g.id = ? AND gm.userid = ?',
269 array($this->item->groupingid, $userid));
270 if (!$gotit && !has_capability('moodle/site:accessallgroups', $context, $userid)) {
271 $available = false;
272 $information .= get_string('groupingnoaccess', 'condition');
273 }
274 } else {
275 // Request is for current user - use cache
94dc3c7d 276 if( !array_key_exists($this->item->course, $CONDITIONLIB_PRIVATE->groupingscache)) {
ce4dfd27 277 if (has_capability('moodle/site:accessallgroups', $context)) {
278 $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = true;
279 } else {
280 $groupings = $DB->get_records_sql('
281 SELECT
282 g.id as gid
283 FROM
284 {groupings} g
285 JOIN {groupings_groups} gg ON g.id = gg.groupingid
286 JOIN {groups_members} gm ON gg.groupid = gm.groupid
287 WHERE
288 g.courseid = ? AND gm.userid = ?',
289 array($this->item->course, $userid));
290 $list = array();
291 foreach ($groupings as $grouping) {
292 $list[$grouping->gid] = true;
293 }
294 $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = $list;
295 }
296 }
297
298 $usergroupings = $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course];
299 if ($usergroupings !== true && !array_key_exists($this->item->groupingid, $usergroupings)) {
300 $available = false;
301 $information .= get_string('groupingnoaccess', 'condition');
302 }
303 }
304 }
305
306 $information = trim($information);
307 return $available;
308 }
309
310 /**
94dc3c7d
EL
311 * Utility function called by modedit.php; updates the
312 * course_modules_availability table based on the module form data.
313 *
314 * @param object $section Section object, must at minimum contain id
315 * @param object $fromform Data from form
316 * @param bool $wipefirst If true, wipes existing conditions
317 */
ce4dfd27 318 public static function update_section_from_form($section, $fromform, $wipefirst=true) {
319 $ci = new condition_info_section($section, CONDITION_MISSING_EVERYTHING);
320 parent::update_from_form($ci, $fromform, $wipefirst);
321 }
322}
323
324
325/**
326 * Base class to handle conditional items of some kind (currently either
327 * course_modules or sections; they must have a corresponding _availability
328 * table).
329 *
330 * @package core_condition
331 * @category condition
332 * @copyright 2012 The Open University
333 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
334 */
335abstract class condition_info_base {
336 /** @var object, bool, string, string, array */
337 protected $item, $gotdata, $availtable, $idfieldname, $usergroupings;
338
339 /**
340 * Constructs with item details.
341 *
342 * @global moodle_database $DB
343 * @uses CONDITION_MISSING_NOTHING
344 * @uses CONDITION_MISSING_EVERYTHING
345 * @uses CONDITION_MISSING_EXTRATABLE
346 * @uses DEBUG_DEVELOPER
347 * @param object $item Object representing some kind of item (cm or section).
348 * May have extra fields ->conditionsgrade, ->conditionscompletion.
349 * Should have ->availablefrom, ->availableuntil, and ->showavailability,
350 * ->course; but the only required thing is ->id.
351 * @param string $tableprefix Prefix for table used to store availability
352 * data, e.g. 'course_modules' if we are going to look at
353 * course_modules_availability.
354 * @param string $idfield Within this table, name of field used as item id
355 * (e.g. 'coursemoduleid')
356 * @param int $expectingmissing Used to control whether or not a developer
357 * debugging message (performance warning) will be displayed if some of
358 * the above data is missing and needs to be retrieved; a
359 * CONDITION_MISSING_xx constant
360 * @param bool $loaddata If you need a 'write-only' object, set this value
361 * to false to prevent database access from constructor
82bd6a5e 362 * @return condition_info Object which can retrieve information about the
363 * activity
364 */
ce4dfd27 365 public function __construct($item, $tableprefix, $idfield, $expectingmissing, $loaddata) {
82bd6a5e 366 global $DB;
367
368 // Check ID as otherwise we can't do the other queries
ce4dfd27 369 if (empty($item->id)) {
370 throw new coding_exception('Invalid parameters; item ID not included');
82bd6a5e 371 }
372
94dc3c7d 373 // DB table to store availability conditions
ce4dfd27 374 $this->availtable = $tableprefix . '_availability';
375
94dc3c7d 376 // Name of module/section ID field in DB
ce4dfd27 377 $this->idfieldname = $idfield;
378
82bd6a5e 379 // If not loading data, don't do anything else
373fb9dd 380 if (!$loaddata) {
ce4dfd27 381 $this->item = (object)array('id' => $item->id);
373fb9dd 382 $this->gotdata = false;
82bd6a5e 383 return;
384 }
385
386 // Missing basic data from course_modules
ce4dfd27 387 $basicfields = $this->get_main_table_fields();
388 $missingbasicfields = false;
389 foreach ($basicfields as $field) {
390 if (!isset($item->{$field})) {
391 $missingbasicfields = true;
392 break;
393 }
394 }
395 if ($missingbasicfields) {
373fb9dd 396 if ($expectingmissing<CONDITION_MISSING_EVERYTHING) {
ce4dfd27 397 debugging('Performance warning: condition_info constructor is ' .
398 'faster if you pass in $item with at least basic fields ' .
399 'from its table. '.
400 '[This warning can be disabled, see phpdoc.]',
401 DEBUG_DEVELOPER);
82bd6a5e 402 }
ce4dfd27 403 $item = $DB->get_record($tableprefix, array('id' => $item->id),
404 implode(',', $basicfields), MUST_EXIST);
82bd6a5e 405 }
406
ce4dfd27 407 $this->item = clone($item);
373fb9dd 408 $this->gotdata = true;
82bd6a5e 409
410 // Missing extra data
ce4dfd27 411 if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
373fb9dd 412 if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
ce4dfd27 413 debugging('Performance warning: condition_info constructor is ' .
414 'faster if you pass in a $item from get_fast_modinfo or ' .
415 'the equivalent for sections. ' .
416 '[This warning can be disabled, see phpdoc.]',
417 DEBUG_DEVELOPER);
82bd6a5e 418 }
419
ce4dfd27 420 $this->fill_availability_conditions($this->item);
82bd6a5e 421 }
422 }
423
424 /**
ce4dfd27 425 * Gets list of required fields from main table.
82bd6a5e 426 *
ce4dfd27 427 * @return array Array of field names
428 */
429 protected function get_main_table_fields() {
540f89d7 430 return array('id', 'course', 'visible',
431 'availablefrom', 'availableuntil', 'showavailability');
ce4dfd27 432 }
433
434 /**
435 * Fills availability conditions into the item object, if they are missing,
436 * otherwise does nothing. Called by subclass fill_availability_conditions.
437 * @param object $item Item object
438 * @param string $tableprefix Prefix of name for _availability table e.g. 'course_modules'
439 * @param string $idfield Name of field that contains id e.g. 'coursemoduleid'
440 * @throws coding_exception If item object doesn't have id field
82bd6a5e 441 */
ce4dfd27 442 protected static function fill_availability_conditions_inner($item, $tableprefix, $idfield) {
443 global $DB, $CFG;
444 if (empty($item->id)) {
445 throw new coding_exception('Invalid parameters; item ID not included');
82bd6a5e 446 }
447
448 // Does nothing if the variables are already present
ce4dfd27 449 if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
450 $item->conditionsgrade = array();
451 $item->conditionscompletion = array();
452
453 $conditions = $DB->get_records_sql('
454 SELECT
455 a.id AS aid, gi.*, a.sourcecmid, a.requiredcompletion, a.gradeitemid,
456 a.grademin as conditiongrademin, a.grademax as conditiongrademax
457 FROM
458 {' . $tableprefix . '_availability} a
459 LEFT JOIN {grade_items} gi ON gi.id = a.gradeitemid
460 WHERE ' . $idfield . ' = ?', array($item->id));
373fb9dd 461 foreach ($conditions as $condition) {
462 if (!is_null($condition->sourcecmid)) {
ce4dfd27 463 $item->conditionscompletion[$condition->sourcecmid] =
82bd6a5e 464 $condition->requiredcompletion;
373fb9dd 465 } else {
466 $minmax = new stdClass;
467 $minmax->min = $condition->conditiongrademin;
468 $minmax->max = $condition->conditiongrademax;
469 $minmax->name = self::get_grade_name($condition);
ce4dfd27 470 $item->conditionsgrade[$condition->gradeitemid] = $minmax;
82bd6a5e 471 }
472 }
473 }
474 }
373fb9dd 475
82bd6a5e 476 /**
477 * Obtains the name of a grade item.
3564771d 478 *
ce4dfd27 479 * @global object $CFG
82bd6a5e 480 * @param object $gradeitemobj Object from get_record on grade_items table,
481 * (can be empty if you want to just get !missing)
482 * @return string Name of item of !missing if it didn't exist
483 */
484 private static function get_grade_name($gradeitemobj) {
485 global $CFG;
373fb9dd 486 if (isset($gradeitemobj->id)) {
ce4dfd27 487 require_once($CFG->libdir . '/gradelib.php');
373fb9dd 488 $item = new grade_item;
117bd748 489 grade_object::set_properties($item, $gradeitemobj);
82bd6a5e 490 return $item->get_name();
491 } else {
492 return '!missing'; // Ooops, missing grade
493 }
494 }
495
496 /**
ce4dfd27 497 * Gets the item object with full necessary data to determine availability.
498 * @return object Item object with full data
499 * @throws coding_exception If data was not supplied when constructing object
82bd6a5e 500 */
ce4dfd27 501 protected function get_full_item() {
82bd6a5e 502 $this->require_data();
ce4dfd27 503 return $this->item;
82bd6a5e 504 }
505
506 /**
507 * Adds to the database a condition based on completion of another module.
3564771d 508 *
9ccadad3 509 * @global moodle_database $DB
82bd6a5e 510 * @param int $cmid ID of other module
511 * @param int $requiredcompletion COMPLETION_xx constant
512 */
373fb9dd 513 public function add_completion_condition($cmid, $requiredcompletion) {
82bd6a5e 514 global $DB;
ce4dfd27 515 // Add to DB
516 $DB->insert_record($this->availtable, (object)array(
517 $this->idfieldname => $this->item->id,
518 'sourcecmid' => $cmid, 'requiredcompletion' => $requiredcompletion),
519 false);
82bd6a5e 520
521 // Store in memory too
ce4dfd27 522 $this->item->conditionscompletion[$cmid] = $requiredcompletion;
82bd6a5e 523 }
524
525 /**
526 * Adds to the database a condition based on the value of a grade item.
3564771d 527 *
9ccadad3 528 * @global moodle_database $DB
82bd6a5e 529 * @param int $gradeitemid ID of grade item
530 * @param float $min Minimum grade (>=), up to 5 decimal points, or null if none
531 * @param float $max Maximum grade (<), up to 5 decimal points, or null if none
532 * @param bool $updateinmemory If true, updates data in memory; otherwise,
533 * memory version may be out of date (this has performance consequences,
534 * so don't do it unless it really needs updating)
535 */
373fb9dd 536 public function add_grade_condition($gradeitemid, $min, $max, $updateinmemory=false) {
ce4dfd27 537 global $DB;
82bd6a5e 538 // Normalise nulls
373fb9dd 539 if ($min==='') {
540 $min = null;
82bd6a5e 541 }
373fb9dd 542 if ($max==='') {
543 $max = null;
82bd6a5e 544 }
545 // Add to DB
ce4dfd27 546 $DB->insert_record($this->availtable, (object)array(
547 $this->idfieldname => $this->item->id,
548 'gradeitemid' => $gradeitemid, 'grademin' => $min, 'grademax' => $max),
549 false);
82bd6a5e 550
551 // Store in memory too
373fb9dd 552 if ($updateinmemory) {
ce4dfd27 553 $this->item->conditionsgrade[$gradeitemid] = (object) array(
554 'min' => $min, 'max' => $max);
555 $this->item->conditionsgrade[$gradeitemid]->name = self::get_grade_name(
556 $DB->get_record('grade_items', array('id'=>$gradeitemid)));
82bd6a5e 557 }
558 }
559
ce4dfd27 560 /**
82bd6a5e 561 * Erases from the database all conditions for this activity.
3564771d 562 *
9ccadad3 563 * @global moodle_database $DB
82bd6a5e 564 */
565 public function wipe_conditions() {
566 // Wipe from DB
567 global $DB;
ce4dfd27 568 $DB->delete_records($this->availtable, array($this->idfieldname => $this->item->id));
82bd6a5e 569
570 // And from memory
ce4dfd27 571 $this->item->conditionsgrade = array();
572 $this->item->conditionscompletion = array();
82bd6a5e 573 }
574
575 /**
576 * Obtains a string describing all availability restrictions (even if
577 * they do not apply any more).
3564771d 578 *
9ccadad3
AKA
579 * @global stdClass $COURSE
580 * @global moodle_database $DB
82bd6a5e 581 * @param object $modinfo Usually leave as null for default. Specify when
117bd748 582 * calling recursively from inside get_fast_modinfo. The value supplied
82bd6a5e 583 * here must include list of all CMs with 'id' and 'name'
117bd748 584 * @return string Information string (for admin) about all restrictions on
82bd6a5e 585 * this item
82bd6a5e 586 */
587 public function get_full_information($modinfo=null) {
373fb9dd 588 global $COURSE, $DB;
ce4dfd27 589 $this->require_data();
82bd6a5e 590
373fb9dd 591 $information = '';
82bd6a5e 592
593 // Completion conditions
ce4dfd27 594 if (count($this->item->conditionscompletion) > 0) {
595 if ($this->item->course == $COURSE->id) {
373fb9dd 596 $course = $COURSE;
82bd6a5e 597 } else {
ce4dfd27 598 $course = $DB->get_record('course', array('id' => $this->item->course),
b884cf2a 599 'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
82bd6a5e 600 }
ce4dfd27 601 foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
373fb9dd 602 if (!$modinfo) {
603 $modinfo = get_fast_modinfo($course);
82bd6a5e 604 }
bfeaa895
SM
605 if (empty($modinfo->cms[$cmid])) {
606 continue;
607 }
373fb9dd 608 $information .= get_string(
ce4dfd27 609 'requires_completion_' . $expectedcompletion,
610 'condition', $modinfo->cms[$cmid]->name) . ' ';
82bd6a5e 611 }
612 }
613
614 // Grade conditions
ce4dfd27 615 if (count($this->item->conditionsgrade) > 0) {
616 foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
82bd6a5e 617 // String depends on type of requirement. We are coy about
618 // the actual numbers, in case grades aren't released to
619 // students.
373fb9dd 620 if (is_null($minmax->min) && is_null($minmax->max)) {
621 $string = 'any';
622 } else if (is_null($minmax->max)) {
623 $string = 'min';
624 } else if (is_null($minmax->min)) {
625 $string = 'max';
82bd6a5e 626 } else {
373fb9dd 627 $string = 'range';
82bd6a5e 628 }
373fb9dd 629 $information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
82bd6a5e 630 }
631 }
632
6282381d 633 // The date logic is complicated. The intention of this logic is:
634 // 1) display date without time where possible (whenever the date is
635 // midnight)
636 // 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as
637 // 'until the 13th' (experience at the OU showed that students are
638 // likely to interpret 'until <date>' as 'until the end of <date>').
639 // 3) This behaviour becomes confusing for 'same-day' dates where there
640 // are some exceptions.
641 // Users in different time zones will typically not get the 'abbreviated'
642 // behaviour but it should work OK for them aside from that.
643
644 // The following cases are possible:
645 // a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
646 // b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
647 // c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
648 // d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct)
649 // e) On 14 Oct (midnight 14 Oct, midnight 15 Oct)
650 // f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day)
651 // g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day)
652 // h) From 13:05 on 14 Oct (exact)
653 // i) From 14 Oct (midnight)
654 // j) Until 13:05 on 14 Oct (exact)
655 // k) Until 14 Oct (midnight 15 Oct)
656
657 // Check if start and end dates are 'midnights', if so we show in short form
ce4dfd27 658 $shortfrom = self::is_midnight($this->item->availablefrom);
659 $shortuntil = self::is_midnight($this->item->availableuntil);
6282381d 660
661 // For some checks and for display, we need the previous day for the 'until'
662 // value, if we are going to display it in short form
ce4dfd27 663 if ($this->item->availableuntil) {
664 $daybeforeuntil = strtotime('-1 day', usergetmidnight($this->item->availableuntil));
6282381d 665 }
666
667 // Special case for if one but not both are exact and they are within a day
ce4dfd27 668 if ($this->item->availablefrom && $this->item->availableuntil &&
669 $shortfrom != $shortuntil && $daybeforeuntil < $this->item->availablefrom) {
6282381d 670 // Don't use abbreviated version (see examples f, g above)
671 $shortfrom = false;
672 $shortuntil = false;
673 }
674
675 // When showing short end date, the display time is the 'day before' one
ce4dfd27 676 $displayuntil = $shortuntil ? $daybeforeuntil : $this->item->availableuntil;
6282381d 677
ce4dfd27 678 if ($this->item->availablefrom && $this->item->availableuntil) {
679 if ($shortfrom && $shortuntil && $daybeforeuntil == $this->item->availablefrom) {
6282381d 680 $information .= get_string('requires_date_both_single_day', 'condition',
ce4dfd27 681 self::show_time($this->item->availablefrom, true));
6282381d 682 } else {
683 $information .= get_string('requires_date_both', 'condition', (object)array(
ce4dfd27 684 'from' => self::show_time($this->item->availablefrom, $shortfrom),
6282381d 685 'until' => self::show_time($displayuntil, $shortuntil)));
686 }
ce4dfd27 687 } else if ($this->item->availablefrom) {
e7c6bf0e 688 $information .= get_string('requires_date', 'condition',
ce4dfd27 689 self::show_time($this->item->availablefrom, $shortfrom));
690 } else if ($this->item->availableuntil) {
e7c6bf0e 691 $information .= get_string('requires_date_before', 'condition',
6282381d 692 self::show_time($displayuntil, $shortuntil));
82bd6a5e 693 }
694
373fb9dd 695 $information = trim($information);
82bd6a5e 696 return $information;
697 }
698
6282381d 699 /**
700 * Checks whether a given time refers exactly to midnight (in current user
701 * timezone).
9ccadad3 702 *
6282381d 703 * @param int $time Time
704 * @return bool True if time refers to midnight, false if it's some other
705 * time or if it is set to zero
706 */
707 private static function is_midnight($time) {
708 return $time && usergetmidnight($time) == $time;
709 }
710
82bd6a5e 711 /**
ce4dfd27 712 * Determines whether this particular item is currently available
117bd748
PS
713 * according to these criteria.
714 *
715 * - This does not include the 'visible' setting (i.e. this might return
82bd6a5e 716 * true even if visible is false); visible is handled independently.
717 * - This does not take account of the viewhiddenactivities capability.
718 * That should apply later.
719 *
9ccadad3
AKA
720 * @global stdClass $COURSE
721 * @global moodle_database $DB
3564771d 722 * @uses COMPLETION_COMPLETE
723 * @uses COMPLETION_COMPLETE_FAIL
724 * @uses COMPLETION_COMPLETE_PASS
725 * @param string $information If the item has availability restrictions,
117bd748 726 * a string that describes the conditions will be stored in this variable;
82bd6a5e 727 * if this variable is set blank, that means don't display anything
117bd748 728 * @param bool $grabthelot Performance hint: if true, caches information
82bd6a5e 729 * required for all course-modules, to make the front page and similar
730 * pages work more quickly (works only for current user)
731 * @param int $userid If set, specifies a different user ID to check availability for
732 * @param object $modinfo Usually leave as null for default. Specify when
117bd748 733 * calling recursively from inside get_fast_modinfo. The value supplied
82bd6a5e 734 * here must include list of all CMs with 'id' and 'name'
735 * @return bool True if this item is available to the user, false otherwise
82bd6a5e 736 */
373fb9dd 737 public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
ce4dfd27 738 global $COURSE, $DB;
373fb9dd 739 $this->require_data();
82bd6a5e 740
373fb9dd 741 $available = true;
742 $information = '';
82bd6a5e 743
744 // Check each completion condition
ce4dfd27 745 if (count($this->item->conditionscompletion) > 0) {
746 if ($this->item->course == $COURSE->id) {
373fb9dd 747 $course = $COURSE;
82bd6a5e 748 } else {
ce4dfd27 749 $course = $DB->get_record('course', array('id' => $this->item->course),
b884cf2a 750 'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
82bd6a5e 751 }
752
373fb9dd 753 $completion = new completion_info($course);
ce4dfd27 754 foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
bfeaa895
SM
755 // If this depends on a deleted module, handle that situation
756 // gracefully.
757 if (!$modinfo) {
758 $modinfo = get_fast_modinfo($course);
759 }
760 if (empty($modinfo->cms[$cmid])) {
72d021a9
SM
761 global $PAGE, $UNITTEST;
762 if (!empty($UNITTEST) || (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-')===0)) {
ce4dfd27 763 debugging("Warning: activity {$this->cm->id} '{$this->cm->name}' has condition " .
764 "on deleted activity $cmid (to get rid of this message, edit the named activity)");
bfeaa895
SM
765 }
766 continue;
767 }
768
82bd6a5e 769 // The completion system caches its own data
ce4dfd27 770 $completiondata = $completion->get_data((object)array('id' => $cmid),
771 $grabthelot, $userid, $modinfo);
82bd6a5e 772
373fb9dd 773 $thisisok = true;
774 if ($expectedcompletion==COMPLETION_COMPLETE) {
82bd6a5e 775 // 'Complete' also allows the pass, fail states
373fb9dd 776 switch ($completiondata->completionstate) {
82bd6a5e 777 case COMPLETION_COMPLETE:
778 case COMPLETION_COMPLETE_FAIL:
779 case COMPLETION_COMPLETE_PASS:
780 break;
781 default:
373fb9dd 782 $thisisok = false;
82bd6a5e 783 }
784 } else {
785 // Other values require exact match
373fb9dd 786 if ($completiondata->completionstate!=$expectedcompletion) {
787 $thisisok = false;
82bd6a5e 788 }
789 }
373fb9dd 790 if (!$thisisok) {
791 $available = false;
373fb9dd 792 $information .= get_string(
ce4dfd27 793 'requires_completion_' . $expectedcompletion,
794 'condition', $modinfo->cms[$cmid]->name) . ' ';
82bd6a5e 795 }
796 }
797 }
798
799 // Check each grade condition
ce4dfd27 800 if (count($this->item->conditionsgrade)>0) {
801 foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
373fb9dd 802 $score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid);
803 if ($score===false ||
ce4dfd27 804 (!is_null($minmax->min) && $score<$minmax->min) ||
805 (!is_null($minmax->max) && $score>=$minmax->max)) {
82bd6a5e 806 // Grade fail
373fb9dd 807 $available = false;
82bd6a5e 808 // String depends on type of requirement. We are coy about
809 // the actual numbers, in case grades aren't released to
810 // students.
373fb9dd 811 if (is_null($minmax->min) && is_null($minmax->max)) {
812 $string = 'any';
813 } else if (is_null($minmax->max)) {
814 $string = 'min';
815 } else if (is_null($minmax->min)) {
816 $string = 'max';
82bd6a5e 817 } else {
373fb9dd 818 $string = 'range';
82bd6a5e 819 }
ce4dfd27 820 $information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' ';
82bd6a5e 821 }
822 }
823 }
824
825 // Test dates
ce4dfd27 826 if ($this->item->availablefrom) {
827 if (time() < $this->item->availablefrom) {
373fb9dd 828 $available = false;
e7c6bf0e 829
830 $information .= get_string('requires_date', 'condition',
ce4dfd27 831 self::show_time($this->item->availablefrom,
832 self::is_midnight($this->item->availablefrom)));
82bd6a5e 833 }
834 }
835
ce4dfd27 836 if ($this->item->availableuntil) {
837 if (time() >= $this->item->availableuntil) {
373fb9dd 838 $available = false;
82bd6a5e 839 // But we don't display any information about this case. This is
840 // because the only reason to set a 'disappear' date is usually
841 // to get rid of outdated information/clutter in which case there
842 // is no point in showing it...
843
844 // Note it would be nice if we could make it so that the 'until'
845 // date appears below the item while the item is still accessible,
846 // unfortunately this is not possible in the current system. Maybe
847 // later, or if somebody else wants to add it.
848 }
849 }
850
540f89d7 851 // If the item is marked as 'not visible' then we don't change the available
852 // flag (visible/available are treated distinctly), but we remove any
853 // availability info. If the item is hidden with the eye icon, it doesn't
854 // make sense to show 'Available from <date>' or similar, because even
855 // when that date arrives it will still not be available unless somebody
856 // toggles the eye icon.
857 if (!$this->item->visible) {
858 $information = '';
859 }
860
ce4dfd27 861 $information = trim($information);
82bd6a5e 862 return $available;
863 }
864
e7c6bf0e 865 /**
6282381d 866 * Shows a time either as a date or a full date and time, according to
867 * user's timezone.
9ccadad3 868 *
e7c6bf0e 869 * @param int $time Time
6282381d 870 * @param bool $dateonly If true, uses date only
e7c6bf0e 871 * @return string Date
872 */
6282381d 873 private function show_time($time, $dateonly) {
874 return userdate($time,
875 get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
e7c6bf0e 876 }
877
82bd6a5e 878 /**
ce4dfd27 879 * Checks whether availability information should be shown to normal users.
9ccadad3 880 *
82bd6a5e 881 * @return bool True if information about availability should be shown to
882 * normal users
883 * @throws coding_exception If data wasn't loaded
884 */
885 public function show_availability() {
886 $this->require_data();
ce4dfd27 887 return $this->item->showavailability;
82bd6a5e 888 }
117bd748 889
82bd6a5e 890 /**
891 * Internal function cheks that data was loaded.
3564771d 892 *
ce4dfd27 893 * @throws coding_exception If data wasn't loaded
82bd6a5e 894 */
895 private function require_data() {
373fb9dd 896 if (!$this->gotdata) {
ce4dfd27 897 throw new coding_exception('Error: cannot call when info was ' .
82bd6a5e 898 'constructed without data');
899 }
900 }
901
94dc3c7d 902 /**
117bd748
PS
903 * Obtains a grade score. Note that this score should not be displayed to
904 * the user, because gradebook rules might prohibit that. It may be a
82bd6a5e 905 * non-final score subject to adjustment later.
906 *
9ccadad3
AKA
907 * @global stdClass $USER
908 * @global moodle_database $DB
909 * @global stdClass $SESSION
82bd6a5e 910 * @param int $gradeitemid Grade item ID we're interested in
117bd748 911 * @param bool $grabthelot If true, grabs all scores for current user on
82bd6a5e 912 * this course, so that later ones come from cache
117bd748 913 * @param int $userid Set if requesting grade for a different user (does
75564228 914 * not use cache)
117bd748 915 * @return float Grade score as a percentage in range 0-100 (e.g. 100.0
75564228 916 * or 37.21), or false if user does not have a grade yet
82bd6a5e 917 */
373fb9dd 918 private function get_cached_grade_score($gradeitemid, $grabthelot=false, $userid=0) {
82bd6a5e 919 global $USER, $DB, $SESSION;
fdef13e6 920 if ($userid==0 || $userid==$USER->id) {
82bd6a5e 921 // For current user, go via cache in session
373fb9dd 922 if (empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
923 $SESSION->gradescorecache = array();
924 $SESSION->gradescorecacheuserid = $USER->id;
117bd748 925 }
373fb9dd 926 if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
927 if ($grabthelot) {
82bd6a5e 928 // Get all grades for the current course
ce4dfd27 929 $rs = $DB->get_recordset_sql('
930 SELECT
931 gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
932 FROM
933 {grade_items} gi
934 LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
935 WHERE
936 gi.courseid = ?', array($USER->id, $this->item->course));
373fb9dd 937 foreach ($rs as $record) {
938 $SESSION->gradescorecache[$record->id] =
82bd6a5e 939 is_null($record->finalgrade)
75564228 940 // No grade = false
117bd748 941 ? false
75564228 942 // Otherwise convert grade to percentage
943 : (($record->finalgrade - $record->rawgrademin) * 100) /
944 ($record->rawgrademax - $record->rawgrademin);
82bd6a5e 945
946 }
947 $rs->close();
948 // And if it's still not set, well it doesn't exist (eg
949 // maybe the user set it as a condition, then deleted the
950 // grade item) so we call it false
373fb9dd 951 if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
952 $SESSION->gradescorecache[$gradeitemid] = false;
82bd6a5e 953 }
954 } else {
955 // Just get current grade
75564228 956 $record = $DB->get_record('grade_grades', array(
373fb9dd 957 'userid'=>$USER->id, 'itemid'=>$gradeitemid));
75564228 958 if ($record && !is_null($record->finalgrade)) {
959 $score = (($record->finalgrade - $record->rawgrademin) * 100) /
960 ($record->rawgrademax - $record->rawgrademin);
961 } else {
962 // Treat the case where row exists but is null, same as
963 // case where row doesn't exist
373fb9dd 964 $score = false;
82bd6a5e 965 }
966 $SESSION->gradescorecache[$gradeitemid]=$score;
967 }
968 }
969 return $SESSION->gradescorecache[$gradeitemid];
970 } else {
971 // Not the current user, so request the score individually
75564228 972 $record = $DB->get_record('grade_grades', array(
973 'userid'=>$userid, 'itemid'=>$gradeitemid));
974 if ($record && !is_null($record->finalgrade)) {
975 $score = (($record->finalgrade - $record->rawgrademin) * 100) /
976 ($record->rawgrademax - $record->rawgrademin);
977 } else {
978 // Treat the case where row exists but is null, same as
979 // case where row doesn't exist
373fb9dd 980 $score = false;
82bd6a5e 981 }
982 return $score;
983 }
984 }
985
117bd748
PS
986 /**
987 * For testing only. Wipes information cached in user session.
3564771d 988 *
9ccadad3 989 * @global stdClass $SESSION
3564771d 990 */
82bd6a5e 991 static function wipe_session_cache() {
992 global $SESSION;
993 unset($SESSION->gradescorecache);
994 unset($SESSION->gradescorecacheuserid);
995 }
996
997 /**
ce4dfd27 998 * Initialises the global cache
999 * @global stdClass $CONDITIONLIB_PRIVATE
1000 */
94dc3c7d 1001 public static function init_global_cache() {
ce4dfd27 1002 global $CONDITIONLIB_PRIVATE;
1003 $CONDITIONLIB_PRIVATE = new stdClass;
1004 $CONDITIONLIB_PRIVATE->usedincondition = array();
1005 $CONDITIONLIB_PRIVATE->groupingscache = array();
1006 }
1007
1008 /**
1009 * Utility function that resets grade/completion conditions in table based
1010 * in data from editing form.
82bd6a5e 1011 *
ce4dfd27 1012 * @param condition_info_base $ci Condition info
1013 * @param object $fromform Data from form
1014 * @param bool $wipefirst If true, wipes existing conditions
82bd6a5e 1015 */
ce4dfd27 1016 protected static function update_from_form(condition_info_base $ci, $fromform, $wipefirst) {
373fb9dd 1017 if ($wipefirst) {
82bd6a5e 1018 $ci->wipe_conditions();
1019 }
373fb9dd 1020 foreach ($fromform->conditiongradegroup as $record) {
82bd6a5e 1021 if($record['conditiongradeitemid']) {
1022 $ci->add_grade_condition($record['conditiongradeitemid'],
ce4dfd27 1023 unformat_float($record['conditiongrademin']), unformat_float($record['conditiongrademax']));
82bd6a5e 1024 }
1025 }
373fb9dd 1026 if(isset ($fromform->conditioncompletiongroup)) {
82bd6a5e 1027 foreach($fromform->conditioncompletiongroup as $record) {
1028 if($record['conditionsourcecmid']) {
1029 $ci->add_completion_condition($record['conditionsourcecmid'],
1030 $record['conditionrequiredcompletion']);
1031 }
1032 }
1033 }
1034 }
1035}
ce4dfd27 1036
1037condition_info::init_global_cache();