MDL-24419 (4): Add conditional availability support to sections; section cache
Credit: original version done by Kirill Astashov of NetSpot (netspot.com.au), finished and tweaked by sam. This change adds conditional availability support for sections analagous to that already available for activities. (Backend, UI, backup/restore.) In order that this feature does not reduce performance, section cacheing has also been added using a new course 'sectioncache' field analagous to modinfo. The new feature integrates with activity availability so that activities inside sections which are not available are automatically not available themselves (meaning it works to restrict access).
This commit is contained in:
+468
-197
@@ -29,59 +29,43 @@ 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);
|
||||
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);
|
||||
define('CONDITION_STUDENTVIEW_SHOW', 1);
|
||||
|
||||
/**
|
||||
* CONDITION_MISSING_NOTHING - The $cm variable is expected to contain all completion-related data
|
||||
* CONDITION_MISSING_NOTHING - The $item variable is expected to contain all completion-related data
|
||||
*/
|
||||
define('CONDITION_MISSING_NOTHING',0);
|
||||
define('CONDITION_MISSING_NOTHING', 0);
|
||||
/**
|
||||
* CONDITION_MISSING_EXTRATABLE - The $cm variable is expected to contain the fields from course_modules
|
||||
* but not the course_modules_availability data
|
||||
* 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);
|
||||
define('CONDITION_MISSING_EXTRATABLE', 1);
|
||||
/**
|
||||
* CONDITION_MISSING_EVERYTHING - The $cm variable is expected to contain nothing except the ID
|
||||
* CONDITION_MISSING_EVERYTHING - The $item variable is expected to contain nothing except the ID
|
||||
*/
|
||||
define('CONDITION_MISSING_EVERYTHING',2);
|
||||
define('CONDITION_MISSING_EVERYTHING', 2);
|
||||
|
||||
require_once($CFG->libdir.'/completionlib.php');
|
||||
|
||||
/**
|
||||
* @global stdClass $CONDITIONLIB_PRIVATE
|
||||
* @name $CONDITIONLIB_PRIVATE
|
||||
*/
|
||||
global $CONDITIONLIB_PRIVATE;
|
||||
$CONDITIONLIB_PRIVATE = new stdClass;
|
||||
// Caches whether completion values are used in availability conditions.
|
||||
// Array of course => array of cmid => true.
|
||||
$CONDITIONLIB_PRIVATE->usedincondition = array();
|
||||
|
||||
/**
|
||||
* Core class to handle conditional activites
|
||||
*
|
||||
* @package core_condition
|
||||
* @category condition
|
||||
* @copyright 2008 Sam Marshall
|
||||
* @copyright 2012 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class condition_info {
|
||||
/** @var object bool */
|
||||
private $cm, $gotdata;
|
||||
|
||||
class condition_info extends condition_info_base {
|
||||
/**
|
||||
* Constructs with course-module details.
|
||||
*
|
||||
* @global moodle_database $DB
|
||||
* @uses CONDITION_MISSING_NOTHING
|
||||
* @uses CONDITION_MISSING_EVERYTHING
|
||||
* @uses DEBUG_DEVELOPER
|
||||
* @uses CONDITION_MISSING_EXTRATABLE
|
||||
* @param object $cm Moodle course-module object. May have extra fields
|
||||
* ->conditionsgrade, ->conditionscompletion which should come from
|
||||
* get_fast_modinfo. Should have ->availablefrom, ->availableuntil,
|
||||
@@ -92,94 +76,396 @@ class condition_info {
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 = context_course::instance($this->item->course);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/** @var object, bool, string, string, array */
|
||||
protected $item, $gotdata, $availtable, $idfieldname, $usergroupings;
|
||||
|
||||
/**
|
||||
* 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($cm, $expectingmissing=CONDITION_MISSING_NOTHING,
|
||||
$loaddata=true) {
|
||||
public function __construct($item, $tableprefix, $idfield, $expectingmissing, $loaddata) {
|
||||
global $DB;
|
||||
|
||||
// Check ID as otherwise we can't do the other queries
|
||||
if (empty($cm->id)) {
|
||||
throw new coding_exception("Invalid parameters; course-module ID not included");
|
||||
if (empty($item->id)) {
|
||||
throw new coding_exception('Invalid parameters; item ID not included');
|
||||
}
|
||||
|
||||
// DB table to store availability conditions
|
||||
$this->availtable = $tableprefix . '_availability';
|
||||
|
||||
// name of module/section ID field in DB
|
||||
$this->idfieldname = $idfield;
|
||||
|
||||
// If not loading data, don't do anything else
|
||||
if (!$loaddata) {
|
||||
$this->cm = (object)array('id'=>$cm->id);
|
||||
$this->item = (object)array('id' => $item->id);
|
||||
$this->gotdata = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Missing basic data from course_modules
|
||||
if (!isset($cm->availablefrom) || !isset($cm->availableuntil) ||
|
||||
!isset($cm->showavailability) || !isset($cm->course)) {
|
||||
if ($expectingmissing<CONDITION_MISSING_EVERYTHING) {
|
||||
debugging('Performance warning: condition_info constructor is
|
||||
faster if you pass in $cm with at least basic fields
|
||||
(availablefrom,availableuntil,showavailability,course).
|
||||
[This warning can be disabled, see phpdoc.]',
|
||||
DEBUG_DEVELOPER);
|
||||
$basicfields = $this->get_main_table_fields();
|
||||
$missingbasicfields = false;
|
||||
foreach ($basicfields as $field) {
|
||||
if (!isset($item->{$field})) {
|
||||
$missingbasicfields = true;
|
||||
break;
|
||||
}
|
||||
$cm = $DB->get_record('course_modules',array('id'=>$cm->id),
|
||||
'id,course,availablefrom,availableuntil,showavailability');
|
||||
}
|
||||
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->cm = clone($cm);
|
||||
$this->item = clone($item);
|
||||
$this->gotdata = true;
|
||||
|
||||
// Missing extra data
|
||||
if (!isset($cm->conditionsgrade) || !isset($cm->conditionscompletion)) {
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
|
||||
if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
|
||||
debugging('Performance warning: condition_info constructor is
|
||||
faster if you pass in a $cm from get_fast_modinfo.
|
||||
[This warning can be disabled, see phpdoc.]',
|
||||
DEBUG_DEVELOPER);
|
||||
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);
|
||||
}
|
||||
|
||||
self::fill_availability_conditions($this->cm);
|
||||
$this->fill_availability_conditions($this->item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the extra availability conditions (if any) into the given
|
||||
* course-module object.
|
||||
* Gets list of required fields from main table.
|
||||
*
|
||||
* @global moodle_database $DB
|
||||
* @global object $CFG
|
||||
* @param object $cm Moodle course-module data object
|
||||
* @return array Array of field names
|
||||
*/
|
||||
public static function fill_availability_conditions(&$cm) {
|
||||
if (empty($cm->id)) {
|
||||
throw new coding_exception("Invalid parameters; course-module ID not included");
|
||||
protected function get_main_table_fields() {
|
||||
return array('id', 'course', '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($cm->conditionsgrade) ||
|
||||
!isset($cm->conditionscompletion)) {
|
||||
$cm->conditionsgrade=array();
|
||||
$cm->conditionscompletion=array();
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
|
||||
$item->conditionsgrade = array();
|
||||
$item->conditionscompletion = array();
|
||||
|
||||
global $DB, $CFG;
|
||||
$conditions = $DB->get_records_sql($sql="
|
||||
SELECT
|
||||
cma.id as cmaid, gi.*,cma.sourcecmid,cma.requiredcompletion,cma.gradeitemid,
|
||||
cma.grademin as conditiongrademin, cma.grademax as conditiongrademax
|
||||
FROM
|
||||
{course_modules_availability} cma
|
||||
LEFT JOIN {grade_items} gi ON gi.id=cma.gradeitemid
|
||||
WHERE
|
||||
coursemoduleid=?",array($cm->id));
|
||||
$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)) {
|
||||
$cm->conditionscompletion[$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);
|
||||
$cm->conditionsgrade[$condition->gradeitemid] = $minmax;
|
||||
$item->conditionsgrade[$condition->gradeitemid] = $minmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +474,7 @@ WHERE
|
||||
/**
|
||||
* Obtains the name of a grade item.
|
||||
*
|
||||
* @global moodle_database $DB
|
||||
* @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
|
||||
@@ -196,7 +482,7 @@ WHERE
|
||||
private static function get_grade_name($gradeitemobj) {
|
||||
global $CFG;
|
||||
if (isset($gradeitemobj->id)) {
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
$item = new grade_item;
|
||||
grade_object::set_properties($item, $gradeitemobj);
|
||||
return $item->get_name();
|
||||
@@ -206,15 +492,13 @@ WHERE
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a wrapper to call require_data()
|
||||
*
|
||||
* @see require_data()
|
||||
* @return object A course-module object with all the information required to
|
||||
* determine availability.
|
||||
* 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
|
||||
*/
|
||||
public function get_full_course_module() {
|
||||
protected function get_full_item() {
|
||||
$this->require_data();
|
||||
return $this->cm;
|
||||
return $this->item;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,15 +509,15 @@ WHERE
|
||||
* @param int $requiredcompletion COMPLETION_xx constant
|
||||
*/
|
||||
public function add_completion_condition($cmid, $requiredcompletion) {
|
||||
// Add to DB
|
||||
global $DB;
|
||||
$DB->insert_record('course_modules_availability',
|
||||
(object)array('coursemoduleid'=>$this->cm->id,
|
||||
'sourcecmid'=>$cmid, 'requiredcompletion'=>$requiredcompletion),
|
||||
false);
|
||||
// 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->cm->conditionscompletion[$cmid] = $requiredcompletion;
|
||||
$this->item->conditionscompletion[$cmid] = $requiredcompletion;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +532,7 @@ WHERE
|
||||
* 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;
|
||||
@@ -256,23 +541,21 @@ WHERE
|
||||
$max = null;
|
||||
}
|
||||
// Add to DB
|
||||
global $DB;
|
||||
$DB->insert_record('course_modules_availability',
|
||||
(object)array('coursemoduleid'=>$this->cm->id,
|
||||
'gradeitemid'=>$gradeitemid, 'grademin'=>$min, 'grademax'=>$max),
|
||||
false);
|
||||
$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->cm->conditionsgrade[$gradeitemid]=(object)array(
|
||||
'min'=>$min, 'max'=>$max);
|
||||
$this->cm->conditionsgrade[$gradeitemid]->name =
|
||||
self::get_grade_name($DB->get_record('grade_items',
|
||||
array('id'=>$gradeitemid)));
|
||||
$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
|
||||
@@ -280,12 +563,11 @@ WHERE
|
||||
public function wipe_conditions() {
|
||||
// Wipe from DB
|
||||
global $DB;
|
||||
$DB->delete_records('course_modules_availability',
|
||||
array('coursemoduleid'=>$this->cm->id));
|
||||
$DB->delete_records($this->availtable, array($this->idfieldname => $this->item->id));
|
||||
|
||||
// And from memory
|
||||
$this->cm->conditionsgrade = array();
|
||||
$this->cm->conditionscompletion = array();
|
||||
$this->item->conditionsgrade = array();
|
||||
$this->item->conditionscompletion = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,19 +583,20 @@ WHERE
|
||||
* this item
|
||||
*/
|
||||
public function get_full_information($modinfo=null) {
|
||||
$this->require_data();
|
||||
global $COURSE, $DB;
|
||||
$this->require_data();
|
||||
|
||||
$information = '';
|
||||
|
||||
// Completion conditions
|
||||
if(count($this->cm->conditionscompletion)>0) {
|
||||
if ($this->cm->course==$COURSE->id) {
|
||||
if (count($this->item->conditionscompletion) > 0) {
|
||||
if ($this->item->course == $COURSE->id) {
|
||||
$course = $COURSE;
|
||||
} else {
|
||||
$course = $DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
|
||||
$course = $DB->get_record('course', array('id' => $this->item->course),
|
||||
'id, enablecompletion, modinfo', MUST_EXIST);
|
||||
}
|
||||
foreach ($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
|
||||
foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
|
||||
if (!$modinfo) {
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
}
|
||||
@@ -321,14 +604,14 @@ WHERE
|
||||
continue;
|
||||
}
|
||||
$information .= get_string(
|
||||
'requires_completion_'.$expectedcompletion,
|
||||
'condition', $modinfo->cms[$cmid]->name).' ';
|
||||
'requires_completion_' . $expectedcompletion,
|
||||
'condition', $modinfo->cms[$cmid]->name) . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Grade conditions
|
||||
if (count($this->cm->conditionsgrade)>0) {
|
||||
foreach ($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
|
||||
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.
|
||||
@@ -370,39 +653,39 @@ WHERE
|
||||
// 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->cm->availablefrom);
|
||||
$shortuntil = self::is_midnight($this->cm->availableuntil);
|
||||
$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->cm->availableuntil) {
|
||||
$daybeforeuntil = strtotime("-1 day", usergetmidnight($this->cm->availableuntil));
|
||||
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->cm->availablefrom && $this->cm->availableuntil &&
|
||||
$shortfrom != $shortuntil && $daybeforeuntil < $this->cm->availablefrom) {
|
||||
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->cm->availableuntil;
|
||||
$displayuntil = $shortuntil ? $daybeforeuntil : $this->item->availableuntil;
|
||||
|
||||
if ($this->cm->availablefrom && $this->cm->availableuntil) {
|
||||
if ($shortfrom && $shortuntil && $daybeforeuntil == $this->cm->availablefrom) {
|
||||
if ($this->item->availablefrom && $this->item->availableuntil) {
|
||||
if ($shortfrom && $shortuntil && $daybeforeuntil == $this->item->availablefrom) {
|
||||
$information .= get_string('requires_date_both_single_day', 'condition',
|
||||
self::show_time($this->cm->availablefrom, true));
|
||||
self::show_time($this->item->availablefrom, true));
|
||||
} else {
|
||||
$information .= get_string('requires_date_both', 'condition', (object)array(
|
||||
'from' => self::show_time($this->cm->availablefrom, $shortfrom),
|
||||
'from' => self::show_time($this->item->availablefrom, $shortfrom),
|
||||
'until' => self::show_time($displayuntil, $shortuntil)));
|
||||
}
|
||||
} else if ($this->cm->availablefrom) {
|
||||
} else if ($this->item->availablefrom) {
|
||||
$information .= get_string('requires_date', 'condition',
|
||||
self::show_time($this->cm->availablefrom, $shortfrom));
|
||||
} else if ($this->cm->availableuntil) {
|
||||
self::show_time($this->item->availablefrom, $shortfrom));
|
||||
} else if ($this->item->availableuntil) {
|
||||
$information .= get_string('requires_date_before', 'condition',
|
||||
self::show_time($displayuntil, $shortuntil));
|
||||
}
|
||||
@@ -424,7 +707,7 @@ WHERE
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this particular course-module is currently available
|
||||
* Determines whether this particular item is currently available
|
||||
* according to these criteria.
|
||||
*
|
||||
* - This does not include the 'visible' setting (i.e. this might return
|
||||
@@ -450,22 +733,23 @@ WHERE
|
||||
* @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();
|
||||
global $COURSE,$DB;
|
||||
|
||||
$available = true;
|
||||
$information = '';
|
||||
|
||||
// Check each completion condition
|
||||
if(count($this->cm->conditionscompletion)>0) {
|
||||
if ($this->cm->course==$COURSE->id) {
|
||||
if (count($this->item->conditionscompletion) > 0) {
|
||||
if ($this->item->course == $COURSE->id) {
|
||||
$course = $COURSE;
|
||||
} else {
|
||||
$course = $DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
|
||||
$course = $DB->get_record('course', array('id' => $this->item->course),
|
||||
'id, enablecompletion, modinfo', MUST_EXIST);
|
||||
}
|
||||
|
||||
$completion = new completion_info($course);
|
||||
foreach ($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
|
||||
foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) {
|
||||
// If this depends on a deleted module, handle that situation
|
||||
// gracefully.
|
||||
if (!$modinfo) {
|
||||
@@ -474,14 +758,15 @@ WHERE
|
||||
if (empty($modinfo->cms[$cmid])) {
|
||||
global $PAGE, $UNITTEST;
|
||||
if (!empty($UNITTEST) || (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-')===0)) {
|
||||
debugging("Warning: activity {$this->cm->id} '{$this->cm->name}' has condition on deleted activity $cmid (to get rid of this message, edit the named activity)");
|
||||
debugging("Warning: activity {$this->cm->id} '{$this->cm->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);
|
||||
$completiondata = $completion->get_data((object)array('id' => $cmid),
|
||||
$grabthelot, $userid, $modinfo);
|
||||
|
||||
$thisisok = true;
|
||||
if ($expectedcompletion==COMPLETION_COMPLETE) {
|
||||
@@ -503,19 +788,19 @@ WHERE
|
||||
if (!$thisisok) {
|
||||
$available = false;
|
||||
$information .= get_string(
|
||||
'requires_completion_'.$expectedcompletion,
|
||||
'condition',$modinfo->cms[$cmid]->name).' ';
|
||||
'requires_completion_' . $expectedcompletion,
|
||||
'condition', $modinfo->cms[$cmid]->name) . ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each grade condition
|
||||
if (count($this->cm->conditionsgrade)>0) {
|
||||
foreach ($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
|
||||
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)) {
|
||||
(!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
|
||||
@@ -530,24 +815,24 @@ WHERE
|
||||
} else {
|
||||
$string = 'range';
|
||||
}
|
||||
$information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
|
||||
$information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test dates
|
||||
if ($this->cm->availablefrom) {
|
||||
if (time() < $this->cm->availablefrom) {
|
||||
if ($this->item->availablefrom) {
|
||||
if (time() < $this->item->availablefrom) {
|
||||
$available = false;
|
||||
|
||||
$information .= get_string('requires_date', 'condition',
|
||||
self::show_time($this->cm->availablefrom,
|
||||
self::is_midnight($this->cm->availablefrom)));
|
||||
self::show_time($this->item->availablefrom,
|
||||
self::is_midnight($this->item->availablefrom)));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->cm->availableuntil) {
|
||||
if (time() >= $this->cm->availableuntil) {
|
||||
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
|
||||
@@ -561,7 +846,7 @@ WHERE
|
||||
}
|
||||
}
|
||||
|
||||
$information=trim($information);
|
||||
$information = trim($information);
|
||||
return $available;
|
||||
}
|
||||
|
||||
@@ -579,7 +864,7 @@ WHERE
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to check if information about availability should be shown to user or not
|
||||
* Checks whether availability information should be shown to normal users.
|
||||
*
|
||||
* @return bool True if information about availability should be shown to
|
||||
* normal users
|
||||
@@ -587,22 +872,22 @@ WHERE
|
||||
*/
|
||||
public function show_availability() {
|
||||
$this->require_data();
|
||||
return $this->cm->showavailability;
|
||||
return $this->item->showavailability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function cheks that data was loaded.
|
||||
*
|
||||
* @return void throws coding_exception If data wasn't 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 '.
|
||||
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.
|
||||
@@ -629,14 +914,14 @@ WHERE
|
||||
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->cm->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)
|
||||
@@ -698,22 +983,32 @@ WHERE
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param bool $wipefirst Defaults to true
|
||||
* Initialises the global cache
|
||||
* @global stdClass $CONDITIONLIB_PRIVATE
|
||||
*/
|
||||
public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
|
||||
$ci=new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
|
||||
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'],
|
||||
$record['conditiongrademin'],$record['conditiongrademax']);
|
||||
unformat_float($record['conditiongrademin']), unformat_float($record['conditiongrademax']));
|
||||
}
|
||||
}
|
||||
if(isset ($fromform->conditioncompletiongroup)) {
|
||||
@@ -725,30 +1020,6 @@ WHERE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $cm 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;
|
||||
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();
|
||||
foreach ($modinfo->cms as $othercm) {
|
||||
foreach ($othercm->conditionscompletion as $cmid=>$expectedcompletion) {
|
||||
$CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_key_exists($cm->id, $CONDITIONLIB_PRIVATE->usedincondition[$course->id]);
|
||||
}
|
||||
}
|
||||
|
||||
condition_info::init_global_cache();
|
||||
|
||||
Reference in New Issue
Block a user