mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
1474 lines
63 KiB
PHP
1474 lines
63 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Used for tracking conditions that apply before activities are displayed
|
|
* to students ('conditional availability').
|
|
*
|
|
* @package core_condition
|
|
* @category condition
|
|
* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* CONDITION_STUDENTVIEW_HIDE - The activity is not displayed to students at all when conditions aren't met.
|
|
*/
|
|
define('CONDITION_STUDENTVIEW_HIDE', 0);
|
|
/**
|
|
* CONDITION_STUDENTVIEW_SHOW - The activity is displayed to students as a greyed-out name, with
|
|
* informational text that explains the conditions under which it will be available.
|
|
*/
|
|
define('CONDITION_STUDENTVIEW_SHOW', 1);
|
|
|
|
/**
|
|
* CONDITION_MISSING_NOTHING - The $item variable is expected to contain all completion-related data
|
|
*/
|
|
define('CONDITION_MISSING_NOTHING', 0);
|
|
/**
|
|
* CONDITION_MISSING_EXTRATABLE - The $item variable is expected to contain the fields from
|
|
* the relevant table (course_modules or course_sections) but not the _availability data
|
|
*/
|
|
define('CONDITION_MISSING_EXTRATABLE', 1);
|
|
/**
|
|
* CONDITION_MISSING_EVERYTHING - The $item variable is expected to contain nothing except the ID
|
|
*/
|
|
define('CONDITION_MISSING_EVERYTHING', 2);
|
|
|
|
/**
|
|
* OP_CONTAINS - comparison operator that determines whether a specified user field contains
|
|
* a provided variable
|
|
*/
|
|
define('OP_CONTAINS', 'contains');
|
|
/**
|
|
* OP_DOES_NOT_CONTAIN - comparison operator that determines whether a specified user field does not
|
|
* contain a provided variable
|
|
*/
|
|
define('OP_DOES_NOT_CONTAIN', 'doesnotcontain');
|
|
/**
|
|
* OP_IS_EQUAL_TO - comparison operator that determines whether a specified user field is equal to
|
|
* a provided variable
|
|
*/
|
|
define('OP_IS_EQUAL_TO', 'isequalto');
|
|
/**
|
|
* OP_STARTS_WITH - comparison operator that determines whether a specified user field starts with
|
|
* a provided variable
|
|
*/
|
|
define('OP_STARTS_WITH', 'startswith');
|
|
/**
|
|
* OP_ENDS_WITH - comparison operator that determines whether a specified user field ends with
|
|
* a provided variable
|
|
*/
|
|
define('OP_ENDS_WITH', 'endswith');
|
|
/**
|
|
* OP_IS_EMPTY - comparison operator that determines whether a specified user field is empty
|
|
*/
|
|
define('OP_IS_EMPTY', 'isempty');
|
|
/**
|
|
* OP_IS_NOT_EMPTY - comparison operator that determines whether a specified user field is not empty
|
|
*/
|
|
define('OP_IS_NOT_EMPTY', 'isnotempty');
|
|
|
|
require_once($CFG->libdir.'/completionlib.php');
|
|
|
|
/**
|
|
* Core class to handle conditional activites
|
|
*
|
|
* @package core_condition
|
|
* @category condition
|
|
* @copyright 2012 The Open University
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class condition_info extends condition_info_base {
|
|
/**
|
|
* Constructs with course-module details.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @uses CONDITION_MISSING_NOTHING
|
|
* @param object $cm Moodle course-module object. May have extra fields
|
|
* ->conditionsgrade, ->conditionscompletion which should come from
|
|
* get_fast_modinfo. Should have ->availablefrom, ->availableuntil,
|
|
* and ->showavailability, ->course, ->visible; but the only required
|
|
* thing is ->id.
|
|
* @param int $expectingmissing Used to control whether or not a developer
|
|
* debugging message (performance warning) will be displayed if some of
|
|
* the above data is missing and needs to be retrieved; a
|
|
* CONDITION_MISSING_xx constant
|
|
* @param bool $loaddata If you need a 'write-only' object, set this value
|
|
* to false to prevent database access from constructor
|
|
*/
|
|
public function __construct($cm, $expectingmissing = CONDITION_MISSING_NOTHING,
|
|
$loaddata=true) {
|
|
parent::__construct($cm, 'course_modules', 'coursemoduleid',
|
|
$expectingmissing, $loaddata);
|
|
}
|
|
|
|
/**
|
|
* Adds the extra availability conditions (if any) into the given
|
|
* course-module (or section) object.
|
|
*
|
|
* This function may be called statically (for the editing form) or
|
|
* dynamically.
|
|
*
|
|
* @param object $cm Moodle course-module data object
|
|
*/
|
|
public static function fill_availability_conditions($cm) {
|
|
parent::fill_availability_conditions_inner($cm, 'course_modules', 'coursemoduleid');
|
|
}
|
|
|
|
/**
|
|
* Gets the course-module object with full necessary data to determine availability.
|
|
* @return object Course-module object with full data
|
|
* @throws coding_exception If data was not supplied when constructing object
|
|
*/
|
|
public function get_full_course_module() {
|
|
return $this->get_full_item();
|
|
}
|
|
|
|
/**
|
|
* Utility function called by modedit.php; updates the
|
|
* course_modules_availability table based on the module form data.
|
|
*
|
|
* @param object $cm Course-module with as much data as necessary, min id
|
|
* @param object $fromform Data from form
|
|
* @param bool $wipefirst If true, wipes existing conditions
|
|
*/
|
|
public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
|
|
$ci = new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
|
|
parent::update_from_form($ci, $fromform, $wipefirst);
|
|
}
|
|
|
|
/**
|
|
* Used in course/lib.php because we need to disable the completion JS if
|
|
* a completion value affects a conditional activity.
|
|
*
|
|
* @global stdClass $CONDITIONLIB_PRIVATE
|
|
* @param object $course Moodle course object
|
|
* @param object $item Moodle course-module
|
|
* @return bool True if this is used in a condition, false otherwise
|
|
*/
|
|
public static function completion_value_used_as_condition($course, $cm) {
|
|
// Have we already worked out a list of required completion values
|
|
// for this course? If so just use that
|
|
global $CONDITIONLIB_PRIVATE, $DB;
|
|
if (!array_key_exists($course->id, $CONDITIONLIB_PRIVATE->usedincondition)) {
|
|
// We don't have data for this course, build it
|
|
$modinfo = get_fast_modinfo($course);
|
|
$CONDITIONLIB_PRIVATE->usedincondition[$course->id] = array();
|
|
|
|
// Activities
|
|
foreach ($modinfo->cms as $othercm) {
|
|
foreach ($othercm->conditionscompletion as $cmid => $expectedcompletion) {
|
|
$CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
|
|
}
|
|
}
|
|
|
|
// Sections
|
|
foreach ($modinfo->get_section_info_all() as $section) {
|
|
foreach ($section->conditionscompletion as $cmid => $expectedcompletion) {
|
|
$CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
|
|
}
|
|
}
|
|
}
|
|
return array_key_exists($cm->id, $CONDITIONLIB_PRIVATE->usedincondition[$course->id]);
|
|
}
|
|
|
|
protected function get_context() {
|
|
return context_module::instance($this->item->id);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles conditional access to sections.
|
|
*
|
|
* @package core_condition
|
|
* @category condition
|
|
* @copyright 2012 The Open University
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class condition_info_section extends condition_info_base {
|
|
/**
|
|
* Constructs with course-module details.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @uses CONDITION_MISSING_NOTHING
|
|
* @param object $section Moodle section object. May have extra fields
|
|
* ->conditionsgrade, ->conditionscompletion. Should have ->availablefrom,
|
|
* ->availableuntil, and ->showavailability, ->course; but the only
|
|
* required thing is ->id.
|
|
* @param int $expectingmissing Used to control whether or not a developer
|
|
* debugging message (performance warning) will be displayed if some of
|
|
* the above data is missing and needs to be retrieved; a
|
|
* CONDITION_MISSING_xx constant
|
|
* @param bool $loaddata If you need a 'write-only' object, set this value
|
|
* to false to prevent database access from constructor
|
|
*/
|
|
public function __construct($section, $expectingmissing = CONDITION_MISSING_NOTHING,
|
|
$loaddata=true) {
|
|
parent::__construct($section, 'course_sections', 'coursesectionid',
|
|
$expectingmissing, $loaddata);
|
|
}
|
|
|
|
/**
|
|
* Adds the extra availability conditions (if any) into the given
|
|
* course-module (or section) object.
|
|
*
|
|
* This function may be called statically (for the editing form) or
|
|
* dynamically.
|
|
*
|
|
* @param object $section Moodle section data object
|
|
*/
|
|
public static function fill_availability_conditions($section) {
|
|
parent::fill_availability_conditions_inner($section, 'course_sections', 'coursesectionid');
|
|
}
|
|
|
|
/**
|
|
* Gets the section object with full necessary data to determine availability.
|
|
* @return object Section object with full data
|
|
* @throws coding_exception If data was not supplied when constructing object
|
|
*/
|
|
public function get_full_section() {
|
|
return $this->get_full_item();
|
|
}
|
|
|
|
/**
|
|
* Gets list of required fields from main table.
|
|
* @return array Array of field names
|
|
*/
|
|
protected function get_main_table_fields() {
|
|
return array_merge(parent::get_main_table_fields(), array('groupingid'));
|
|
}
|
|
|
|
/**
|
|
* Determines whether this particular section is currently available
|
|
* according to these criteria.
|
|
*
|
|
* - This does not include the 'visible' setting (i.e. this might return
|
|
* true even if visible is false); visible is handled independently.
|
|
* - This does not take account of the viewhiddenactivities capability.
|
|
* That should apply later.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @global stdclass $USER
|
|
* @param string $information If the item has availability restrictions,
|
|
* a string that describes the conditions will be stored in this variable;
|
|
* if this variable is set blank, that means don't display anything
|
|
* @param bool $grabthelot Performance hint: if true, caches information
|
|
* required for all course-modules, to make the front page and similar
|
|
* pages work more quickly (works only for current user)
|
|
* @param int $userid If set, specifies a different user ID to check availability for
|
|
* @param object $modinfo Usually leave as null for default. Specify when
|
|
* calling recursively from inside get_fast_modinfo. The value supplied
|
|
* here must include list of all CMs with 'id' and 'name'
|
|
* @return bool True if this item is available to the user, false otherwise
|
|
*/
|
|
public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
|
|
global $DB, $USER, $CONDITIONLIB_PRIVATE;
|
|
|
|
$available = parent::is_available($information, $grabthelot, $userid, $modinfo);
|
|
|
|
// test if user is enrolled to a grouping which has access to the section
|
|
if (!empty($this->item->groupingid)) {
|
|
// Get real user id
|
|
if (!$userid) {
|
|
$userid = $USER->id;
|
|
}
|
|
$context = $this->get_context();
|
|
|
|
if ($userid != $USER->id) {
|
|
// We are requesting for a non-current user so check it individually
|
|
// (no cache). Do grouping check first, it's probably faster
|
|
// than the capability check
|
|
$gotit = $DB->record_exists_sql('
|
|
SELECT
|
|
1
|
|
FROM
|
|
{groupings} g
|
|
JOIN {groupings_groups} gg ON g.id = gg.groupingid
|
|
JOIN {groups_members} gm ON gg.groupid = gm.groupid
|
|
WHERE
|
|
g.id = ? AND gm.userid = ?',
|
|
array($this->item->groupingid, $userid));
|
|
if (!$gotit && !has_capability('moodle/site:accessallgroups', $context, $userid)) {
|
|
$available = false;
|
|
$information .= get_string('groupingnoaccess', 'condition');
|
|
}
|
|
} else {
|
|
// Request is for current user - use cache
|
|
if( !array_key_exists($this->item->course, $CONDITIONLIB_PRIVATE->groupingscache)) {
|
|
if (has_capability('moodle/site:accessallgroups', $context)) {
|
|
$CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = true;
|
|
} else {
|
|
$groupings = $DB->get_records_sql('
|
|
SELECT
|
|
g.id as gid
|
|
FROM
|
|
{groupings} g
|
|
JOIN {groupings_groups} gg ON g.id = gg.groupingid
|
|
JOIN {groups_members} gm ON gg.groupid = gm.groupid
|
|
WHERE
|
|
g.courseid = ? AND gm.userid = ?',
|
|
array($this->item->course, $userid));
|
|
$list = array();
|
|
foreach ($groupings as $grouping) {
|
|
$list[$grouping->gid] = true;
|
|
}
|
|
$CONDITIONLIB_PRIVATE->groupingscache[$this->item->course] = $list;
|
|
}
|
|
}
|
|
|
|
$usergroupings = $CONDITIONLIB_PRIVATE->groupingscache[$this->item->course];
|
|
if ($usergroupings !== true && !array_key_exists($this->item->groupingid, $usergroupings)) {
|
|
$available = false;
|
|
$information .= get_string('groupingnoaccess', 'condition');
|
|
}
|
|
}
|
|
}
|
|
|
|
$information = trim($information);
|
|
return $available;
|
|
}
|
|
|
|
/**
|
|
* Utility function called by modedit.php; updates the
|
|
* course_modules_availability table based on the module form data.
|
|
*
|
|
* @param object $section Section object, must at minimum contain id
|
|
* @param object $fromform Data from form
|
|
* @param bool $wipefirst If true, wipes existing conditions
|
|
*/
|
|
public static function update_section_from_form($section, $fromform, $wipefirst=true) {
|
|
$ci = new condition_info_section($section, CONDITION_MISSING_EVERYTHING);
|
|
parent::update_from_form($ci, $fromform, $wipefirst);
|
|
}
|
|
|
|
protected function get_context() {
|
|
return context_course::instance($this->item->course);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Base class to handle conditional items of some kind (currently either
|
|
* course_modules or sections; they must have a corresponding _availability
|
|
* table).
|
|
*
|
|
* @package core_condition
|
|
* @category condition
|
|
* @copyright 2012 The Open University
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
abstract class condition_info_base {
|
|
protected $item;
|
|
/** @var bool */
|
|
protected $gotdata;
|
|
/** @var string */
|
|
protected $availtable;
|
|
/** @var string */
|
|
protected $availfieldtable;
|
|
/** @var string */
|
|
protected $idfieldname;
|
|
/** @var array */
|
|
protected $usergroupings;
|
|
/** @var array An array of custom profile field ids => to their shortname */
|
|
protected $customprofilefields = null;
|
|
/**
|
|
* Constructs with item details.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @uses CONDITION_MISSING_NOTHING
|
|
* @uses CONDITION_MISSING_EVERYTHING
|
|
* @uses CONDITION_MISSING_EXTRATABLE
|
|
* @uses DEBUG_DEVELOPER
|
|
* @param object $item Object representing some kind of item (cm or section).
|
|
* May have extra fields ->conditionsgrade, ->conditionscompletion.
|
|
* Should have ->availablefrom, ->availableuntil, and ->showavailability,
|
|
* ->course; but the only required thing is ->id.
|
|
* @param string $tableprefix Prefix for table used to store availability
|
|
* data, e.g. 'course_modules' if we are going to look at
|
|
* course_modules_availability.
|
|
* @param string $idfield Within this table, name of field used as item id
|
|
* (e.g. 'coursemoduleid')
|
|
* @param int $expectingmissing Used to control whether or not a developer
|
|
* debugging message (performance warning) will be displayed if some of
|
|
* the above data is missing and needs to be retrieved; a
|
|
* CONDITION_MISSING_xx constant
|
|
* @param bool $loaddata If you need a 'write-only' object, set this value
|
|
* to false to prevent database access from constructor
|
|
* @return condition_info Object which can retrieve information about the
|
|
* activity
|
|
*/
|
|
public function __construct($item, $tableprefix, $idfield, $expectingmissing, $loaddata) {
|
|
global $DB;
|
|
|
|
// Check ID as otherwise we can't do the other queries
|
|
if (empty($item->id)) {
|
|
throw new coding_exception('Invalid parameters; item ID not included');
|
|
}
|
|
|
|
// DB table to store availability conditions
|
|
$this->availtable = $tableprefix . '_availability';
|
|
|
|
// DB table to store availability conditions for user fields
|
|
$this->availfieldtable = $tableprefix . '_avail_fields';
|
|
|
|
// Name of module/section ID field in DB
|
|
$this->idfieldname = $idfield;
|
|
|
|
// If not loading data, don't do anything else
|
|
if (!$loaddata) {
|
|
$this->item = (object)array('id' => $item->id);
|
|
$this->gotdata = false;
|
|
return;
|
|
}
|
|
|
|
// Missing basic data from course_modules
|
|
$basicfields = $this->get_main_table_fields();
|
|
$missingbasicfields = false;
|
|
foreach ($basicfields as $field) {
|
|
if (!isset($item->{$field})) {
|
|
$missingbasicfields = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($missingbasicfields) {
|
|
if ($expectingmissing<CONDITION_MISSING_EVERYTHING) {
|
|
debugging('Performance warning: condition_info constructor is ' .
|
|
'faster if you pass in $item with at least basic fields ' .
|
|
'from its table. '.
|
|
'[This warning can be disabled, see phpdoc.]',
|
|
DEBUG_DEVELOPER);
|
|
}
|
|
$item = $DB->get_record($tableprefix, array('id' => $item->id),
|
|
implode(',', $basicfields), MUST_EXIST);
|
|
}
|
|
|
|
$this->item = clone($item);
|
|
$this->gotdata = true;
|
|
|
|
// Missing extra data
|
|
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion) || !isset($item->conditionsfield)) {
|
|
if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
|
|
debugging('Performance warning: condition_info constructor is ' .
|
|
'faster if you pass in a $item from get_fast_modinfo or ' .
|
|
'the equivalent for sections. ' .
|
|
'[This warning can be disabled, see phpdoc.]',
|
|
DEBUG_DEVELOPER);
|
|
}
|
|
|
|
$this->fill_availability_conditions($this->item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets list of required fields from main table.
|
|
*
|
|
* @return array Array of field names
|
|
*/
|
|
protected function get_main_table_fields() {
|
|
return array('id', 'course', 'visible',
|
|
'availablefrom', 'availableuntil', 'showavailability');
|
|
}
|
|
|
|
/**
|
|
* Fills availability conditions into the item object, if they are missing,
|
|
* otherwise does nothing. Called by subclass fill_availability_conditions.
|
|
* @param object $item Item object
|
|
* @param string $tableprefix Prefix of name for _availability table e.g. 'course_modules'
|
|
* @param string $idfield Name of field that contains id e.g. 'coursemoduleid'
|
|
* @throws coding_exception If item object doesn't have id field
|
|
*/
|
|
protected static function fill_availability_conditions_inner($item, $tableprefix, $idfield) {
|
|
global $DB, $CFG;
|
|
if (empty($item->id)) {
|
|
throw new coding_exception('Invalid parameters; item ID not included');
|
|
}
|
|
|
|
// Does nothing if the variables are already present
|
|
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion) || !isset($item->conditionsfield)) {
|
|
$item->conditionsgrade = array();
|
|
$item->conditionscompletion = array();
|
|
$item->conditionsfield = array();
|
|
|
|
$conditions = $DB->get_records_sql('
|
|
SELECT
|
|
a.id AS aid, gi.*, a.sourcecmid, a.requiredcompletion, a.gradeitemid,
|
|
a.grademin as conditiongrademin, a.grademax as conditiongrademax
|
|
FROM
|
|
{' . $tableprefix . '_availability} a
|
|
LEFT JOIN {grade_items} gi ON gi.id = a.gradeitemid
|
|
WHERE ' . $idfield . ' = ?', array($item->id));
|
|
foreach ($conditions as $condition) {
|
|
if (!is_null($condition->sourcecmid)) {
|
|
$item->conditionscompletion[$condition->sourcecmid] =
|
|
$condition->requiredcompletion;
|
|
} else {
|
|
$minmax = new stdClass;
|
|
$minmax->min = $condition->conditiongrademin;
|
|
$minmax->max = $condition->conditiongrademax;
|
|
$minmax->name = self::get_grade_name($condition);
|
|
$item->conditionsgrade[$condition->gradeitemid] = $minmax;
|
|
}
|
|
}
|
|
|
|
// For user fields
|
|
$sql = "SELECT a.id as cmaid, a.*, uf.*
|
|
FROM {" . $tableprefix . "_avail_fields} a
|
|
LEFT JOIN {user_info_field} uf ON a.customfieldid = uf.id
|
|
WHERE " . $idfield . " = :itemid";
|
|
$conditions = $DB->get_records_sql($sql, array('itemid' => $item->id));
|
|
foreach ($conditions as $condition) {
|
|
// If the custom field is not empty, then we have a custom profile field
|
|
if (!empty($condition->customfieldid)) {
|
|
$field = $condition->customfieldid;
|
|
// Check if the profile field name is not empty, if it is
|
|
// then the custom profile field no longer exists, so
|
|
// display !missing instead.
|
|
if (!empty($condition->name)) {
|
|
$fieldname = $condition->name;
|
|
} else {
|
|
$fieldname = '!missing';
|
|
}
|
|
} else {
|
|
$field = $condition->userfield;
|
|
$fieldname = $condition->userfield;
|
|
}
|
|
$details = new stdClass;
|
|
$details->fieldname = $fieldname;
|
|
$details->operator = $condition->operator;
|
|
$details->value = $condition->value;
|
|
$item->conditionsfield[$field] = $details;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtains the name of a grade item.
|
|
*
|
|
* @global object $CFG
|
|
* @param object $gradeitemobj Object from get_record on grade_items table,
|
|
* (can be empty if you want to just get !missing)
|
|
* @return string Name of item of !missing if it didn't exist
|
|
*/
|
|
private static function get_grade_name($gradeitemobj) {
|
|
global $CFG;
|
|
if (isset($gradeitemobj->id)) {
|
|
require_once($CFG->libdir . '/gradelib.php');
|
|
$item = new grade_item;
|
|
grade_object::set_properties($item, $gradeitemobj);
|
|
return $item->get_name();
|
|
} else {
|
|
return '!missing'; // Ooops, missing grade
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the item object with full necessary data to determine availability.
|
|
* @return object Item object with full data
|
|
* @throws coding_exception If data was not supplied when constructing object
|
|
*/
|
|
protected function get_full_item() {
|
|
$this->require_data();
|
|
return $this->item;
|
|
}
|
|
|
|
/**
|
|
* The operators that provide the relationship
|
|
* between a field and a value.
|
|
*
|
|
* @return array Associative array from operator constant to display name
|
|
*/
|
|
public static function get_condition_user_field_operators() {
|
|
return array(
|
|
OP_CONTAINS => get_string('contains', 'condition'),
|
|
OP_DOES_NOT_CONTAIN => get_string('doesnotcontain', 'condition'),
|
|
OP_IS_EQUAL_TO => get_string('isequalto', 'condition'),
|
|
OP_STARTS_WITH => get_string('startswith', 'condition'),
|
|
OP_ENDS_WITH => get_string('endswith', 'condition'),
|
|
OP_IS_EMPTY => get_string('isempty', 'condition'),
|
|
OP_IS_NOT_EMPTY => get_string('isnotempty', 'condition'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns list of user fields that can be compared.
|
|
*
|
|
* If you specify $formatoptions, then format_string will be called on the
|
|
* custom field names. This is necessary for multilang support to work so
|
|
* you should include this parameter unless you are going to format the
|
|
* text later.
|
|
*
|
|
* @param array $formatoptions Passed to format_string if provided
|
|
* @return array Associative array from user field constants to display name
|
|
*/
|
|
public static function get_condition_user_fields($formatoptions = null) {
|
|
global $DB;
|
|
|
|
$userfields = array(
|
|
'firstname' => get_user_field_name('firstname'),
|
|
'lastname' => get_user_field_name('lastname'),
|
|
'email' => get_user_field_name('email'),
|
|
'city' => get_user_field_name('city'),
|
|
'country' => get_user_field_name('country'),
|
|
'url' => get_user_field_name('url'),
|
|
'icq' => get_user_field_name('icq'),
|
|
'skype' => get_user_field_name('skype'),
|
|
'aim' => get_user_field_name('aim'),
|
|
'yahoo' => get_user_field_name('yahoo'),
|
|
'msn' => get_user_field_name('msn'),
|
|
'idnumber' => get_user_field_name('idnumber'),
|
|
'institution' => get_user_field_name('institution'),
|
|
'department' => get_user_field_name('department'),
|
|
'phone1' => get_user_field_name('phone1'),
|
|
'phone2' => get_user_field_name('phone2'),
|
|
'address' => get_user_field_name('address')
|
|
);
|
|
|
|
// Go through the custom profile fields now
|
|
if ($user_info_fields = $DB->get_records('user_info_field')) {
|
|
foreach ($user_info_fields as $field) {
|
|
if ($formatoptions) {
|
|
$userfields[$field->id] = format_string($field->name, true, $formatoptions);
|
|
} else {
|
|
$userfields[$field->id] = $field->name;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $userfields;
|
|
}
|
|
|
|
/**
|
|
* Adds to the database a condition based on completion of another module.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @param int $cmid ID of other module
|
|
* @param int $requiredcompletion COMPLETION_xx constant
|
|
*/
|
|
public function add_completion_condition($cmid, $requiredcompletion) {
|
|
global $DB;
|
|
// Add to DB
|
|
$DB->insert_record($this->availtable, (object)array(
|
|
$this->idfieldname => $this->item->id,
|
|
'sourcecmid' => $cmid, 'requiredcompletion' => $requiredcompletion),
|
|
false);
|
|
|
|
// Store in memory too
|
|
$this->item->conditionscompletion[$cmid] = $requiredcompletion;
|
|
}
|
|
|
|
/**
|
|
* Adds user fields condition
|
|
*
|
|
* @param mixed $field numeric if it is a user profile field, character
|
|
* if it is a column in the user table
|
|
* @param int $operator specifies the relationship between field and value
|
|
* @param char $value the value of the field
|
|
*/
|
|
public function add_user_field_condition($field, $operator, $value) {
|
|
global $DB;
|
|
|
|
// Get the field name
|
|
$idfieldname = $this->idfieldname;
|
|
|
|
$objavailfield = new stdClass;
|
|
$objavailfield->$idfieldname = $this->item->id;
|
|
if (is_numeric($field)) { // If the condition field is numeric then it is a custom profile field
|
|
// Need to get the field name so we can add it to the cache
|
|
$ufield = $DB->get_field('user_info_field', 'name', array('id' => $field));
|
|
$objavailfield->fieldname = $ufield;
|
|
$objavailfield->customfieldid = $field;
|
|
} else {
|
|
$objavailfield->fieldname = $field;
|
|
$objavailfield->userfield = $field;
|
|
}
|
|
$objavailfield->operator = $operator;
|
|
$objavailfield->value = $value;
|
|
$DB->insert_record($this->availfieldtable, $objavailfield, false);
|
|
|
|
// Store in memory too
|
|
$this->item->conditionsfield[$field] = $objavailfield;
|
|
}
|
|
|
|
/**
|
|
* Adds to the database a condition based on the value of a grade item.
|
|
*
|
|
* @global moodle_database $DB
|
|
* @param int $gradeitemid ID of grade item
|
|
* @param float $min Minimum grade (>=), up to 5 decimal points, or null if none
|
|
* @param float $max Maximum grade (<), up to 5 decimal points, or null if none
|
|
* @param bool $updateinmemory If true, updates data in memory; otherwise,
|
|
* memory version may be out of date (this has performance consequences,
|
|
* so don't do it unless it really needs updating)
|
|
*/
|
|
public function add_grade_condition($gradeitemid, $min, $max, $updateinmemory=false) {
|
|
global $DB;
|
|
// Normalise nulls
|
|
if ($min==='') {
|
|
$min = null;
|
|
}
|
|
if ($max==='') {
|
|
$max = null;
|
|
}
|
|
// Add to DB
|
|
$DB->insert_record($this->availtable, (object)array(
|
|
$this->idfieldname => $this->item->id,
|
|
'gradeitemid' => $gradeitemid, 'grademin' => $min, 'grademax' => $max),
|
|
false);
|
|
|
|
// Store in memory too
|
|
if ($updateinmemory) {
|
|
$this->item->conditionsgrade[$gradeitemid] = (object) array(
|
|
'min' => $min, 'max' => $max);
|
|
$this->item->conditionsgrade[$gradeitemid]->name = self::get_grade_name(
|
|
$DB->get_record('grade_items', array('id'=>$gradeitemid)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erases from the database all conditions for this activity.
|
|
*
|
|
* @global moodle_database $DB
|
|
*/
|
|
public function wipe_conditions() {
|
|
// Wipe from DB
|
|
global $DB;
|
|
|
|
$DB->delete_records($this->availtable, array($this->idfieldname => $this->item->id));
|
|
$DB->delete_records($this->availfieldtable, array($this->idfieldname => $this->item->id));
|
|
|
|
// And from memory
|
|
$this->item->conditionsgrade = array();
|
|
$this->item->conditionscompletion = array();
|
|
$this->item->conditionsfield = array();
|
|
}
|
|
|
|
/**
|
|
* Obtains a string describing all availability restrictions (even if
|
|
* they do not apply any more).
|
|
*
|
|
* @global stdClass $COURSE
|
|
* @global moodle_database $DB
|
|
* @param object $modinfo Usually leave as null for default. Specify when
|
|
* calling recursively from inside get_fast_modinfo. The value supplied
|
|
* here must include list of all CMs with 'id' and 'name'
|
|
* @return string Information string (for admin) about all restrictions on
|
|
* this item
|
|
*/
|
|
public function get_full_information($modinfo=null) {
|
|
global $COURSE, $DB;
|
|
$this->require_data();
|
|
|
|
$information = '';
|
|
|
|
|
|
// Completion conditions
|
|
if (count($this->item->conditionscompletion) > 0) {
|
|
if ($this->item->course == $COURSE->id) {
|
|
$course = $COURSE;
|
|
} else {
|
|
$course = $DB->get_record('course', array('id' => $this->item->course),
|
|
'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
|
|
}
|
|
foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
|
|
if (!$modinfo) {
|
|
$modinfo = get_fast_modinfo($course);
|
|
}
|
|
if (empty($modinfo->cms[$cmid])) {
|
|
continue;
|
|
}
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string(
|
|
'requires_completion_' . $expectedcompletion,
|
|
'condition', $modinfo->cms[$cmid]->name) . ' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
|
|
// Grade conditions
|
|
if (count($this->item->conditionsgrade) > 0) {
|
|
foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
|
|
// String depends on type of requirement. We are coy about
|
|
// the actual numbers, in case grades aren't released to
|
|
// students.
|
|
if (is_null($minmax->min) && is_null($minmax->max)) {
|
|
$string = 'any';
|
|
} else if (is_null($minmax->max)) {
|
|
$string = 'min';
|
|
} else if (is_null($minmax->min)) {
|
|
$string = 'max';
|
|
} else {
|
|
$string = 'range';
|
|
}
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
|
|
// User field conditions
|
|
if (count($this->item->conditionsfield) > 0) {
|
|
$context = $this->get_context();
|
|
// Need the array of operators
|
|
foreach ($this->item->conditionsfield as $field => $details) {
|
|
$a = new stdclass;
|
|
$a->field = format_string($details->fieldname, true, array('context' => $context));
|
|
$a->value = $details->value;
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_user_field_'.$details->operator, 'condition', $a) . ' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
|
|
// The date logic is complicated. The intention of this logic is:
|
|
// 1) display date without time where possible (whenever the date is
|
|
// midnight)
|
|
// 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as
|
|
// 'until the 13th' (experience at the OU showed that students are
|
|
// likely to interpret 'until <date>' as 'until the end of <date>').
|
|
// 3) This behaviour becomes confusing for 'same-day' dates where there
|
|
// are some exceptions.
|
|
// Users in different time zones will typically not get the 'abbreviated'
|
|
// behaviour but it should work OK for them aside from that.
|
|
|
|
// The following cases are possible:
|
|
// a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
|
|
// b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
|
|
// c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
|
|
// d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct)
|
|
// e) On 14 Oct (midnight 14 Oct, midnight 15 Oct)
|
|
// f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day)
|
|
// g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day)
|
|
// h) From 13:05 on 14 Oct (exact)
|
|
// i) From 14 Oct (midnight)
|
|
// j) Until 13:05 on 14 Oct (exact)
|
|
// k) Until 14 Oct (midnight 15 Oct)
|
|
|
|
// Check if start and end dates are 'midnights', if so we show in short form
|
|
$shortfrom = self::is_midnight($this->item->availablefrom);
|
|
$shortuntil = self::is_midnight($this->item->availableuntil);
|
|
|
|
// For some checks and for display, we need the previous day for the 'until'
|
|
// value, if we are going to display it in short form
|
|
if ($this->item->availableuntil) {
|
|
$daybeforeuntil = strtotime('-1 day', usergetmidnight($this->item->availableuntil));
|
|
}
|
|
|
|
// Special case for if one but not both are exact and they are within a day
|
|
if ($this->item->availablefrom && $this->item->availableuntil &&
|
|
$shortfrom != $shortuntil && $daybeforeuntil < $this->item->availablefrom) {
|
|
// Don't use abbreviated version (see examples f, g above)
|
|
$shortfrom = false;
|
|
$shortuntil = false;
|
|
}
|
|
|
|
// When showing short end date, the display time is the 'day before' one
|
|
$displayuntil = $shortuntil ? $daybeforeuntil : $this->item->availableuntil;
|
|
|
|
if ($this->item->availablefrom && $this->item->availableuntil) {
|
|
if ($shortfrom && $shortuntil && $daybeforeuntil == $this->item->availablefrom) {
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_date_both_single_day', 'condition',
|
|
self::show_time($this->item->availablefrom, true));
|
|
$information .= html_writer::end_tag('li');
|
|
} else {
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_date_both', 'condition', (object)array(
|
|
'from' => self::show_time($this->item->availablefrom, $shortfrom),
|
|
'until' => self::show_time($displayuntil, $shortuntil)));
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
} else if ($this->item->availablefrom) {
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_date', 'condition',
|
|
self::show_time($this->item->availablefrom, $shortfrom));
|
|
$information .= html_writer::end_tag('li');
|
|
} else if ($this->item->availableuntil) {
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_date_before', 'condition',
|
|
self::show_time($displayuntil, $shortuntil));
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
|
|
// The information is in <li> tags, but to avoid taking up more space
|
|
// if there is only a single item, we strip out the list tags so that it
|
|
// is plain text in that case.
|
|
if (!empty($information)) {
|
|
$li = strpos($information, '<li>', 4);
|
|
if ($li === false) {
|
|
$information = preg_replace('~^<li>(.*)</li>$~', '$1', $information);
|
|
} else {
|
|
$information = html_writer::tag('ul', $information);
|
|
}
|
|
$information = trim($information);
|
|
}
|
|
return $information;
|
|
}
|
|
|
|
/**
|
|
* Checks whether a given time refers exactly to midnight (in current user
|
|
* timezone).
|
|
*
|
|
* @param int $time Time
|
|
* @return bool True if time refers to midnight, false if it's some other
|
|
* time or if it is set to zero
|
|
*/
|
|
private static function is_midnight($time) {
|
|
return $time && usergetmidnight($time) == $time;
|
|
}
|
|
|
|
/**
|
|
* Determines whether this particular item is currently available
|
|
* according to these criteria.
|
|
*
|
|
* - This does not include the 'visible' setting (i.e. this might return
|
|
* true even if visible is false); visible is handled independently.
|
|
* - This does not take account of the viewhiddenactivities capability.
|
|
* That should apply later.
|
|
*
|
|
* @global stdClass $COURSE
|
|
* @global moodle_database $DB
|
|
* @uses COMPLETION_COMPLETE
|
|
* @uses COMPLETION_COMPLETE_FAIL
|
|
* @uses COMPLETION_COMPLETE_PASS
|
|
* @param string $information If the item has availability restrictions,
|
|
* a string that describes the conditions will be stored in this variable;
|
|
* if this variable is set blank, that means don't display anything
|
|
* @param bool $grabthelot Performance hint: if true, caches information
|
|
* required for all course-modules, to make the front page and similar
|
|
* pages work more quickly (works only for current user)
|
|
* @param int $userid If set, specifies a different user ID to check availability for
|
|
* @param object $modinfo Usually leave as null for default. Specify when
|
|
* calling recursively from inside get_fast_modinfo. The value supplied
|
|
* here must include list of all CMs with 'id' and 'name'
|
|
* @return bool True if this item is available to the user, false otherwise
|
|
*/
|
|
public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
|
|
global $COURSE, $DB;
|
|
$this->require_data();
|
|
|
|
$available = true;
|
|
$information = '';
|
|
|
|
// Check each completion condition
|
|
if (count($this->item->conditionscompletion) > 0) {
|
|
if ($this->item->course == $COURSE->id) {
|
|
$course = $COURSE;
|
|
} else {
|
|
$course = $DB->get_record('course', array('id' => $this->item->course),
|
|
'id, enablecompletion, modinfo, sectioncache', MUST_EXIST);
|
|
}
|
|
|
|
$completion = new completion_info($course);
|
|
foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
|
|
// If this depends on a deleted module, handle that situation
|
|
// gracefully.
|
|
if (!$modinfo) {
|
|
$modinfo = get_fast_modinfo($course);
|
|
}
|
|
if (empty($modinfo->cms[$cmid])) {
|
|
global $PAGE;
|
|
if (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-')===0) {
|
|
debugging("Warning: activity {$this->item->id} '{$this->item->name}' has condition " .
|
|
"on deleted activity $cmid (to get rid of this message, edit the named activity)");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// The completion system caches its own data
|
|
$completiondata = $completion->get_data((object)array('id' => $cmid),
|
|
$grabthelot, $userid, $modinfo);
|
|
|
|
$thisisok = true;
|
|
if ($expectedcompletion==COMPLETION_COMPLETE) {
|
|
// 'Complete' also allows the pass, fail states
|
|
switch ($completiondata->completionstate) {
|
|
case COMPLETION_COMPLETE:
|
|
case COMPLETION_COMPLETE_FAIL:
|
|
case COMPLETION_COMPLETE_PASS:
|
|
break;
|
|
default:
|
|
$thisisok = false;
|
|
}
|
|
} else {
|
|
// Other values require exact match
|
|
if ($completiondata->completionstate!=$expectedcompletion) {
|
|
$thisisok = false;
|
|
}
|
|
}
|
|
if (!$thisisok) {
|
|
$available = false;
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string(
|
|
'requires_completion_' . $expectedcompletion,
|
|
'condition', $modinfo->cms[$cmid]->name) . ' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check each grade condition
|
|
if (count($this->item->conditionsgrade)>0) {
|
|
foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) {
|
|
$score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid);
|
|
if ($score===false ||
|
|
(!is_null($minmax->min) && $score<$minmax->min) ||
|
|
(!is_null($minmax->max) && $score>=$minmax->max)) {
|
|
// Grade fail
|
|
$available = false;
|
|
// String depends on type of requirement. We are coy about
|
|
// the actual numbers, in case grades aren't released to
|
|
// students.
|
|
if (is_null($minmax->min) && is_null($minmax->max)) {
|
|
$string = 'any';
|
|
} else if (is_null($minmax->max)) {
|
|
$string = 'min';
|
|
} else if (is_null($minmax->min)) {
|
|
$string = 'max';
|
|
} else {
|
|
$string = 'range';
|
|
}
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if user field condition
|
|
if (count($this->item->conditionsfield) > 0) {
|
|
$context = $this->get_context();
|
|
foreach ($this->item->conditionsfield as $field => $details) {
|
|
$uservalue = $this->get_cached_user_profile_field($userid, $field);
|
|
if (!$this->is_field_condition_met($details->operator, $uservalue, $details->value)) {
|
|
// Set available to false
|
|
$available = false;
|
|
$a = new stdClass();
|
|
$a->field = format_string($details->fieldname, true, array('context' => $context));
|
|
$a->value = $details->value;
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_user_field_'.$details->operator, 'condition', $a) . ' ';
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test dates
|
|
if ($this->item->availablefrom) {
|
|
if (time() < $this->item->availablefrom) {
|
|
$available = false;
|
|
|
|
$information .= html_writer::start_tag('li');
|
|
$information .= get_string('requires_date', 'condition',
|
|
self::show_time($this->item->availablefrom,
|
|
self::is_midnight($this->item->availablefrom)));
|
|
$information .= html_writer::end_tag('li');
|
|
}
|
|
}
|
|
|
|
if ($this->item->availableuntil) {
|
|
if (time() >= $this->item->availableuntil) {
|
|
$available = false;
|
|
// But we don't display any information about this case. This is
|
|
// because the only reason to set a 'disappear' date is usually
|
|
// to get rid of outdated information/clutter in which case there
|
|
// is no point in showing it...
|
|
|
|
// Note it would be nice if we could make it so that the 'until'
|
|
// date appears below the item while the item is still accessible,
|
|
// unfortunately this is not possible in the current system. Maybe
|
|
// later, or if somebody else wants to add it.
|
|
}
|
|
}
|
|
|
|
// If the item is marked as 'not visible' then we don't change the available
|
|
// flag (visible/available are treated distinctly), but we remove any
|
|
// availability info. If the item is hidden with the eye icon, it doesn't
|
|
// make sense to show 'Available from <date>' or similar, because even
|
|
// when that date arrives it will still not be available unless somebody
|
|
// toggles the eye icon.
|
|
if (!$this->item->visible) {
|
|
$information = '';
|
|
}
|
|
|
|
// The information is in <li> tags, but to avoid taking up more space
|
|
// if there is only a single item, we strip out the list tags so that it
|
|
// is plain text in that case.
|
|
if (!empty($information)) {
|
|
$li = strpos($information, '<li>', 4);
|
|
if ($li === false) {
|
|
$information = preg_replace('~^<li>(.*)</li>$~', '$1', $information);
|
|
} else {
|
|
$information = html_writer::tag('ul', $information);
|
|
}
|
|
$information = trim($information);
|
|
}
|
|
return $available;
|
|
}
|
|
|
|
/**
|
|
* Shows a time either as a date or a full date and time, according to
|
|
* user's timezone.
|
|
*
|
|
* @param int $time Time
|
|
* @param bool $dateonly If true, uses date only
|
|
* @return string Date
|
|
*/
|
|
private function show_time($time, $dateonly) {
|
|
return userdate($time,
|
|
get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
|
|
}
|
|
|
|
/**
|
|
* Checks whether availability information should be shown to normal users.
|
|
*
|
|
* @return bool True if information about availability should be shown to
|
|
* normal users
|
|
* @throws coding_exception If data wasn't loaded
|
|
*/
|
|
public function show_availability() {
|
|
$this->require_data();
|
|
return $this->item->showavailability;
|
|
}
|
|
|
|
/**
|
|
* Internal function cheks that data was loaded.
|
|
*
|
|
* @throws coding_exception If data wasn't loaded
|
|
*/
|
|
private function require_data() {
|
|
if (!$this->gotdata) {
|
|
throw new coding_exception('Error: cannot call when info was ' .
|
|
'constructed without data');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtains a grade score. Note that this score should not be displayed to
|
|
* the user, because gradebook rules might prohibit that. It may be a
|
|
* non-final score subject to adjustment later.
|
|
*
|
|
* @global stdClass $USER
|
|
* @global moodle_database $DB
|
|
* @global stdClass $SESSION
|
|
* @param int $gradeitemid Grade item ID we're interested in
|
|
* @param bool $grabthelot If true, grabs all scores for current user on
|
|
* this course, so that later ones come from cache
|
|
* @param int $userid Set if requesting grade for a different user (does
|
|
* not use cache)
|
|
* @return float Grade score as a percentage in range 0-100 (e.g. 100.0
|
|
* or 37.21), or false if user does not have a grade yet
|
|
*/
|
|
private function get_cached_grade_score($gradeitemid, $grabthelot=false, $userid=0) {
|
|
global $USER, $DB, $SESSION;
|
|
if ($userid==0 || $userid==$USER->id) {
|
|
// For current user, go via cache in session
|
|
if (empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
|
|
$SESSION->gradescorecache = array();
|
|
$SESSION->gradescorecacheuserid = $USER->id;
|
|
}
|
|
if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
|
|
if ($grabthelot) {
|
|
// Get all grades for the current course
|
|
$rs = $DB->get_recordset_sql('
|
|
SELECT
|
|
gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
|
|
FROM
|
|
{grade_items} gi
|
|
LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
|
|
WHERE
|
|
gi.courseid = ?', array($USER->id, $this->item->course));
|
|
foreach ($rs as $record) {
|
|
$SESSION->gradescorecache[$record->id] =
|
|
is_null($record->finalgrade)
|
|
// No grade = false
|
|
? false
|
|
// Otherwise convert grade to percentage
|
|
: (($record->finalgrade - $record->rawgrademin) * 100) /
|
|
($record->rawgrademax - $record->rawgrademin);
|
|
|
|
}
|
|
$rs->close();
|
|
// And if it's still not set, well it doesn't exist (eg
|
|
// maybe the user set it as a condition, then deleted the
|
|
// grade item) so we call it false
|
|
if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
|
|
$SESSION->gradescorecache[$gradeitemid] = false;
|
|
}
|
|
} else {
|
|
// Just get current grade
|
|
$record = $DB->get_record('grade_grades', array(
|
|
'userid'=>$USER->id, 'itemid'=>$gradeitemid));
|
|
if ($record && !is_null($record->finalgrade)) {
|
|
$score = (($record->finalgrade - $record->rawgrademin) * 100) /
|
|
($record->rawgrademax - $record->rawgrademin);
|
|
} else {
|
|
// Treat the case where row exists but is null, same as
|
|
// case where row doesn't exist
|
|
$score = false;
|
|
}
|
|
$SESSION->gradescorecache[$gradeitemid]=$score;
|
|
}
|
|
}
|
|
return $SESSION->gradescorecache[$gradeitemid];
|
|
} else {
|
|
// Not the current user, so request the score individually
|
|
$record = $DB->get_record('grade_grades', array(
|
|
'userid'=>$userid, 'itemid'=>$gradeitemid));
|
|
if ($record && !is_null($record->finalgrade)) {
|
|
$score = (($record->finalgrade - $record->rawgrademin) * 100) /
|
|
($record->rawgrademax - $record->rawgrademin);
|
|
} else {
|
|
// Treat the case where row exists but is null, same as
|
|
// case where row doesn't exist
|
|
$score = false;
|
|
}
|
|
return $score;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if a field meets the required conditions, false otherwise.
|
|
*
|
|
* @param string $operator the requirement/condition
|
|
* @param string $uservalue the user's value
|
|
* @param string $value the value required
|
|
* @return boolean
|
|
*/
|
|
private function is_field_condition_met($operator, $uservalue, $value) {
|
|
if ($uservalue === false) {
|
|
// If the user value is false this is an instant fail.
|
|
// All user values come from the database as either data or the default.
|
|
// They will always be a string.
|
|
return false;
|
|
}
|
|
$fieldconditionmet = true;
|
|
// Just to be doubly sure it is a string.
|
|
$uservalue = (string)$uservalue;
|
|
switch($operator) {
|
|
case OP_CONTAINS: // contains
|
|
$pos = strpos($uservalue, $value);
|
|
if ($pos === false) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
case OP_DOES_NOT_CONTAIN: // does not contain
|
|
if (!empty($value)) {
|
|
$pos = strpos($uservalue, $value);
|
|
if ($pos !== false) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
}
|
|
break;
|
|
case OP_IS_EQUAL_TO: // equal to
|
|
if ($value !== $uservalue) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
case OP_STARTS_WITH: // starts with
|
|
$length = strlen($value);
|
|
if ((substr($uservalue, 0, $length) !== $value)) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
case OP_ENDS_WITH: // ends with
|
|
$length = strlen($value);
|
|
$start = $length * -1; // negative
|
|
if (substr($uservalue, $start) !== $value) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
case OP_IS_EMPTY: // is empty
|
|
if (!empty($uservalue)) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
case OP_IS_NOT_EMPTY: // is not empty
|
|
if (empty($uservalue)) {
|
|
$fieldconditionmet = false;
|
|
}
|
|
break;
|
|
}
|
|
return $fieldconditionmet;
|
|
}
|
|
|
|
/**
|
|
* Return the value for a user's profile field
|
|
*
|
|
* @param int $userid set if requesting grade for a different user (does not use cache)
|
|
* @param int $fieldid the user profile field id
|
|
* @return string the user value, or false if user does not have a user field value yet
|
|
*/
|
|
protected function get_cached_user_profile_field($userid, $fieldid) {
|
|
global $USER, $DB, $CFG;
|
|
|
|
if ($userid === 0) {
|
|
// Map out userid = 0 to the current user
|
|
$userid = $USER->id;
|
|
}
|
|
$iscurrentuser = $USER->id == $userid;
|
|
|
|
if (isguestuser($userid)) {
|
|
// Must be logged in and can't be the guest. (this should never happen anyway)
|
|
return false;
|
|
}
|
|
|
|
// Custom profile fields will be numeric, there are no numeric standard profile fields so this is not a problem.
|
|
$iscustomprofilefield = is_numeric($fieldid);
|
|
if ($iscustomprofilefield) {
|
|
// As its a custom profile field we need to map the id back to the actual field.
|
|
// We'll also preload all of the other custom profile fields just in case and ensure we have the
|
|
// default value available as well.
|
|
if ($this->customprofilefields === null) {
|
|
$this->customprofilefields = $DB->get_records('user_info_field', null, 'sortorder ASC, id ASC', 'id, shortname, defaultdata');
|
|
}
|
|
if (!array_key_exists($fieldid, $this->customprofilefields)) {
|
|
// No such field exists.
|
|
// This shouldn't normally happen but occur if things go wrong when deleting a custom profile field
|
|
// or when restoring a backup of a course with user profile field conditions.
|
|
return false;
|
|
}
|
|
$field = $this->customprofilefields[$fieldid]->shortname;
|
|
} else {
|
|
$field = $fieldid;
|
|
}
|
|
|
|
// If its the current user than most likely we will be able to get this information from $USER.
|
|
// If its a regular profile field then it should already be available, if not then we have a mega problem.
|
|
// If its a custom profile field then it should be available but may not be. If it is then we use the value
|
|
// available, otherwise we load all custom profile fields into a temp object and refer to that.
|
|
// Noting its not going be great for performance if we have to use the temp object as it involves loading the
|
|
// custom profile field API and classes.
|
|
if ($iscurrentuser) {
|
|
if (!$iscustomprofilefield) {
|
|
if (property_exists($USER, $field)) {
|
|
return $USER->{$field};
|
|
} else {
|
|
// Unknown user field. This should not happen.
|
|
throw new coding_exception('Requested user profile field does not exist');
|
|
}
|
|
}
|
|
// Checking if the custom profile fields are already available.
|
|
if (!isset($USER->profile)) {
|
|
// Drat! they're not. We need to use a temp object and load them.
|
|
// We don't use $USER as the profile fields are loaded into the object.
|
|
$user = new stdClass;
|
|
$user->id = $USER->id;
|
|
// This should ALWAYS be set, but just in case we check.
|
|
require_once($CFG->dirroot.'/user/profile/lib.php');
|
|
profile_load_custom_fields($user);
|
|
if (array_key_exists($field, $user->profile)) {
|
|
return $user->profile[$field];
|
|
}
|
|
} else if (array_key_exists($field, $USER->profile)) {
|
|
// Hurrah they're available, this is easy.
|
|
return $USER->profile[$field];
|
|
}
|
|
// The profile field doesn't exist.
|
|
return false;
|
|
} else {
|
|
// Loading for another user.
|
|
if ($iscustomprofilefield) {
|
|
// Fetch the data for the field. Noting we keep this query simple so that Database caching takes care of performance
|
|
// for us (this will likely be hit again).
|
|
// We are able to do this because we've already pre-loaded the custom fields.
|
|
$data = $DB->get_field('user_info_data', 'data', array('userid' => $userid, 'fieldid' => $fieldid), IGNORE_MISSING);
|
|
// If we have data return that, otherwise return the default.
|
|
if ($data !== false) {
|
|
return $data;
|
|
} else {
|
|
return $this->customprofilefields[$field]->defaultdata;
|
|
}
|
|
} else {
|
|
// Its a standard field, retrieve it from the user.
|
|
return $DB->get_field('user', $field, array('id' => $userid), MUST_EXIST);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* For testing only. Wipes information cached in user session.
|
|
*
|
|
* @global stdClass $SESSION
|
|
*/
|
|
static function wipe_session_cache() {
|
|
global $SESSION;
|
|
unset($SESSION->gradescorecache);
|
|
unset($SESSION->gradescorecacheuserid);
|
|
unset($SESSION->userfieldcache);
|
|
unset($SESSION->userfieldcacheuserid);
|
|
}
|
|
|
|
/**
|
|
* Initialises the global cache
|
|
* @global stdClass $CONDITIONLIB_PRIVATE
|
|
*/
|
|
public static function init_global_cache() {
|
|
global $CONDITIONLIB_PRIVATE;
|
|
$CONDITIONLIB_PRIVATE = new stdClass;
|
|
$CONDITIONLIB_PRIVATE->usedincondition = array();
|
|
$CONDITIONLIB_PRIVATE->groupingscache = array();
|
|
}
|
|
|
|
/**
|
|
* Utility function that resets grade/completion conditions in table based
|
|
* in data from editing form.
|
|
*
|
|
* @param condition_info_base $ci Condition info
|
|
* @param object $fromform Data from form
|
|
* @param bool $wipefirst If true, wipes existing conditions
|
|
*/
|
|
protected static function update_from_form(condition_info_base $ci, $fromform, $wipefirst) {
|
|
if ($wipefirst) {
|
|
$ci->wipe_conditions();
|
|
}
|
|
foreach ($fromform->conditiongradegroup as $record) {
|
|
if($record['conditiongradeitemid']) {
|
|
$ci->add_grade_condition($record['conditiongradeitemid'],
|
|
unformat_float($record['conditiongrademin']), unformat_float($record['conditiongrademax']));
|
|
}
|
|
}
|
|
foreach ($fromform->conditionfieldgroup as $record) {
|
|
if($record['conditionfield']) {
|
|
$ci->add_user_field_condition($record['conditionfield'],
|
|
$record['conditionfieldoperator'],
|
|
$record['conditionfieldvalue']);
|
|
}
|
|
}
|
|
if(isset ($fromform->conditioncompletiongroup)) {
|
|
foreach($fromform->conditioncompletiongroup as $record) {
|
|
if($record['conditionsourcecmid']) {
|
|
$ci->add_completion_condition($record['conditionsourcecmid'],
|
|
$record['conditionrequiredcompletion']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtains context for any necessary checks.
|
|
*
|
|
* @return context Suitable context for the item
|
|
*/
|
|
protected abstract function get_context();
|
|
}
|
|
|
|
condition_info::init_global_cache();
|