MDL-41218 Object section_info calculates expensive data on demand only

Added phpdocs and unittests, removed unnecessary fields such as course and userid because they can be retrieved from modinfo
This commit is contained in:
Marina Glancy
2013-09-10 14:11:24 +10:00
parent 840bbc1b64
commit 4e4309064d
2 changed files with 276 additions and 83 deletions
+194 -83
View File
@@ -350,15 +350,8 @@ class course_modinfo extends stdClass {
// Expand section objects
$this->sectioninfo = array();
foreach ($sectioncache as $number => $data) {
// Calculate sequence
if (isset($this->sections[$number])) {
$sequence = implode(',', $this->sections[$number]);
} else {
$sequence = '';
}
// Expand
$this->sectioninfo[$number] = new section_info($data, $number, $course->id, $sequence,
$this, $userid);
$this->sectioninfo[$number] = new section_info($data, $number, null, null,
$this, null);
}
// We need at least 'dynamic' data from each course-module (this is basically the remaining
@@ -1175,6 +1168,7 @@ class cm_info extends stdClass {
$userid = $this->modinfo->get_user_id();
if (!empty($CFG->enableavailability)) {
require_once($CFG->libdir. '/conditionlib.php');
// Get availability information
$ci = new condition_info($this);
// Note that the modinfo currently available only includes minimal details (basic data)
@@ -1290,6 +1284,8 @@ class cm_info extends stdClass {
return false;
}
require_once($CFG->libdir. '/conditionlib.php');
// If module will always be visible anyway (but greyed out), don't bother checking anything else
if ($this->showavailability == CONDITION_STUDENTVIEW_SHOW) {
return false;
@@ -1590,6 +1586,40 @@ class cached_cm_info {
/**
* Data about a single section on a course. This contains the fields from the
* course_sections table, plus additional data when required.
*
* @property-read int $id Section ID - from course_sections table
* @property-read int $course Course ID - from course_sections table
* @property-read int $section Section number - from course_sections table
* @property-read string $name Section name if specified - from course_sections table
* @property-read int $visible Section visibility (1 = visible) - from course_sections table
* @property-read string $summary Section summary text if specified - from course_sections table
* @property-read int $summaryformat Section summary text format (FORMAT_xx constant) - from course_sections table
* @property-read int $showavailability When section is unavailable, this field controls whether it is shown to students (0 =
* hide completely, 1 = show greyed out with information about when it will be available) -
* from course_sections table
* @property-read int $availablefrom Available date for this section (0 if not set, or set to seconds since epoch;
* before this date, section does not display to students) - from course_sections table
* @property-read int $availableuntil Available until date for this section (0 if not set, or set to seconds since epoch;
* from this date, section does not display to students) - from course_sections table
* @property-read int $groupingid If section is restricted to users of a particular grouping, this is its id (0 if not set) -
* from course_sections table
* @property-read array $conditionscompletion Availability conditions for this section based on the completion of
* course-modules (array from course-module id to required completion state
* for that module) - from cached data in sectioncache field
* @property-read array $conditionsgrade Availability conditions for this section based on course grades (array from
* grade item id to object with ->min, ->max fields) - from cached data in
* sectioncache field
* @property-read array $conditionsfield Availability conditions for this section based on user fields
* @property-read bool $available True if this section is available to the given user i.e. if all availability conditions
* are met - obtained dynamically
* @property-read string $availableinfo If section is not available to some users, this string gives information about
* availability which can be displayed to students and/or staff (e.g. 'Available from 3 January 2010')
* for display on main page - obtained dynamically
* @property-read bool $uservisible True if this section is available to the given user (for example, if current user
* has viewhiddensections capability, they can access the section even if it is not
* visible or not available, so this would be true in that case) - obtained dynamically
* @property-read string $sequence Comma-separated list of all modules in the section. Note, this field may not exactly
* match course_sections.sequence if later has references to non-existing modules or not modules of not available module types.
*/
class section_info implements IteratorAggregate {
/**
@@ -1598,12 +1628,6 @@ class section_info implements IteratorAggregate {
*/
private $_id;
/**
* Course ID - from course_sections table
* @var int
*/
private $_course;
/**
* Section number - from course_sections table
* @var int
@@ -1687,15 +1711,16 @@ class section_info implements IteratorAggregate {
/**
* True if this section is available to students i.e. if all availability conditions
* are met - obtained dynamically
* @var bool
* are met - obtained dynamically on request, see function {@link section_info::get_available()}
* @var bool|null
*/
private $_available;
/**
* If section is not available to students, this string gives information about
* If section is not available to some users, this string gives information about
* availability which can be displayed to students and/or staff (e.g. 'Available from 3
* January 2010') for display on main page - obtained dynamically
* January 2010') for display on main page - obtained dynamically on request, see
* function {@link section_info::get_availableinfo()}
* @var string
*/
private $_availableinfo;
@@ -1703,8 +1728,9 @@ class section_info implements IteratorAggregate {
/**
* True if this section is available to the CURRENT user (for example, if current user
* has viewhiddensections capability, they can access the section even if it is not
* visible or not available, so this would be true in that case)
* @var bool
* visible or not available, so this would be true in that case) - obtained dynamically
* on request, see function {@link section_info::get_uservisible()}
* @var bool|null
*/
private $_uservisible;
@@ -1732,16 +1758,30 @@ class section_info implements IteratorAggregate {
*/
private $cachedformatoptions = array();
/**
* Stores the list of all possible section options defined in each used course format.
* @var array
*/
static private $sectionformatoptions = array();
/**
* Stores the modinfo object passed in constructor, may be used when requesting
* dynamically obtained attributes such as available, availableinfo, uservisible.
* Also used to retrun information about current course or user.
* @var course_modinfo
*/
private $modinfo;
/**
* Constructs object from database information plus extra required data.
* @param object $data Array entry from cached sectioncache
* @param int $number Section number (array key)
* @param int $courseid Course ID
* @param int $sequence Sequence of course-module ids contained within
* @param int $notused1 argument not used (informaion is available in $modinfo)
* @param int $notused2 argument not used (informaion is available in $modinfo)
* @param course_modinfo $modinfo Owner (needed for checking availability)
* @param int $userid User ID
* @param int $notused3 argument not used (informaion is available in $modinfo)
*/
public function __construct($data, $number, $courseid, $sequence, $modinfo, $userid) {
public function __construct($data, $number, $notused1, $notused2, $modinfo, $notused3) {
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
@@ -1762,9 +1802,20 @@ class section_info implements IteratorAggregate {
}
}
// cached course format data
$formatoptionsdef = course_get_format($courseid)->section_format_options();
foreach ($formatoptionsdef as $field => $option) {
// Other data from constructor arguments.
$this->_section = $number;
$this->modinfo = $modinfo;
// Cached course format data.
$course = $modinfo->get_course();
if (!isset($course->format) || !isset(self::$sectionformatoptions[$course->format])) {
$courseformat = course_get_format(isset($course->format) ? $course : $course->id);
if (!isset($course->format)) {
$course->format = $courseformat->get_format();
}
self::$sectionformatoptions[$course->format] = $courseformat->section_format_options();
}
foreach (self::$sectionformatoptions[$course->format] as $field => $option) {
if (!empty($option['cache'])) {
if (isset($data->{$field})) {
$this->cachedformatoptions[$field] = $data->{$field};
@@ -1773,37 +1824,6 @@ class section_info implements IteratorAggregate {
}
}
}
// Other data from other places
$this->_course = $courseid;
$this->_section = $number;
$this->_sequence = $sequence;
// Availability data
if (!empty($CFG->enableavailability)) {
require_once($CFG->libdir. '/conditionlib.php');
// Get availability information
$ci = new condition_info_section($this);
$this->_available = $ci->is_available($this->_availableinfo, true,
$userid, $modinfo);
// Display grouping info if available & not already displaying
// (it would already display if current user doesn't have access)
// for people with managegroups - same logic/class as grouping label
// on individual activities.
$context = context_course::instance($courseid);
if ($this->_availableinfo === '' && $this->_groupingid &&
has_capability('moodle/course:managegroups', $context)) {
$groupings = groups_get_all_groupings($courseid);
$this->_availableinfo = html_writer::tag('span', '(' . format_string(
$groupings[$this->_groupingid]->name, true, array('context' => $context)) .
')', array('class' => 'groupinglabel'));
}
} else {
$this->_available = true;
}
// Update visibility for current user
$this->update_user_visible($userid);
}
/**
@@ -1813,11 +1833,9 @@ class section_info implements IteratorAggregate {
* @return bool
*/
public function __isset($name) {
if (property_exists($this, '_'.$name)) {
return isset($this->{'_'.$name});
}
$defaultformatoptions = course_get_format($this->_course)->section_format_options();
if (array_key_exists($name, $defaultformatoptions)) {
if (method_exists($this, 'get_'.$name) ||
property_exists($this, '_'.$name) ||
array_key_exists($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
$value = $this->__get($name);
return isset($value);
}
@@ -1831,11 +1849,9 @@ class section_info implements IteratorAggregate {
* @return bool
*/
public function __empty($name) {
if (property_exists($this, '_'.$name)) {
return empty($this->{'_'.$name});
}
$defaultformatoptions = course_get_format($this->_course)->section_format_options();
if (array_key_exists($name, $defaultformatoptions)) {
if (method_exists($this, 'get_'.$name) ||
property_exists($this, '_'.$name) ||
in_array($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
$value = $this->__get($name);
return empty($value);
}
@@ -1850,22 +1866,83 @@ class section_info implements IteratorAggregate {
* @return bool
*/
public function __get($name) {
if (method_exists($this, 'get_'.$name)) {
return $this->{'get_'.$name}();
}
if (property_exists($this, '_'.$name)) {
return $this->{'_'.$name};
}
if (array_key_exists($name, $this->cachedformatoptions)) {
return $this->cachedformatoptions[$name];
}
$defaultformatoptions = course_get_format($this->_course)->section_format_options();
// precheck if the option is defined in format to avoid unnecessary DB queries in get_format_options()
if (array_key_exists($name, $defaultformatoptions)) {
$formatoptions = course_get_format($this->_course)->get_format_options($this);
if (in_array($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
$formatoptions = course_get_format($this->modinfo->get_course())->get_format_options($this);
return $formatoptions[$name];
}
debugging('Invalid section_info property accessed! '.$name);
return null;
}
/**
* Finds whether this section is available at the moment for the current user.
*
* The value can be accessed publicly as $sectioninfo->available
*
* @return bool
*/
private function get_available() {
global $CFG;
if ($this->_available !== null) {
// Has already been calculated.
return $this->_available;
}
if (!empty($CFG->enableavailability)) {
require_once($CFG->libdir. '/conditionlib.php');
// Get availability information
$ci = new condition_info_section($this);
$this->_available = $ci->is_available($this->_availableinfo, true,
$this->modinfo->get_user_id(), $this->modinfo);
if ($this->_availableinfo === '' && $this->_groupingid) {
// Still may have some extra text in availableinfo because of groupping.
// Set as undefined so the next call to get_availabeinfo() calculates it.
$this->_availableinfo = null;
}
} else {
$this->_available = true;
$this->_availableinfo = '';
}
return $this->_available;
}
/**
* Returns the availability text shown next to the section on course page.
*
* @return string
*/
private function get_availableinfo() {
// Make sure $this->_available has been calculated, it may also fill the _availableinfo property.
$this->get_available();
if ($this->_availableinfo !== null) {
// It has been already calculated.
return $this->_availableinfo;
}
$this->_availableinfo = '';
// Display grouping info if available & not already displaying
// (it would already display if current user doesn't have access)
// for people with managegroups - same logic/class as grouping label
// on individual activities.
$context = context_course::instance($this->get_course());
$userid = $this->modinfo->get_user_id();
if ($this->_groupingid && has_capability('moodle/course:managegroups', $context, $userid)) {
$groupings = groups_get_all_groupings($this->get_course());
$this->_availableinfo = html_writer::tag('span', '(' . format_string(
$groupings[$this->_groupingid]->name, true, array('context' => $context)) .
')', array('class' => 'groupinglabel'));
}
return $this->_availableinfo;
}
/**
* Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
* and use {@link convert_to_array()}
@@ -1876,27 +1953,61 @@ class section_info implements IteratorAggregate {
$ret = array();
foreach (get_object_vars($this) as $key => $value) {
if (substr($key, 0, 1) == '_') {
$ret[substr($key, 1)] = $this->$key;
if (method_exists($this, 'get'.$key)) {
$ret[substr($key, 1)] = $this->{'get'.$key}();
} else {
$ret[substr($key, 1)] = $this->$key;
}
}
}
$ret = array_merge($ret, course_get_format($this->_course)->get_format_options($this));
$ret['sequence'] = $this->get_sequence();
$ret['course'] = $this->get_course();
$ret = array_merge($ret, course_get_format($this->modinfo->get_course())->get_format_options($this->_section));
return new ArrayIterator($ret);
}
/**
* Works out whether activity is visible *for current user* - if this is false, they
* aren't allowed to access it.
* @param int $userid User ID
* @return void
*
* @return bool
*/
private function update_user_visible($userid) {
global $CFG;
$coursecontext = context_course::instance($this->_course);
$this->_uservisible = true;
if ((!$this->_visible || !$this->_available) &&
!has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)) {
$this->_uservisible = false;
private function get_uservisible() {
if ($this->_uservisible !== null) {
// Has already been calculated.
return $this->_uservisible;
}
$this->_uservisible = true;
if (!$this->_visible || !$this->get_available()) {
$coursecontext = context_course::instance($this->get_course());
$userid = $this->modinfo->get_user_id();
if (!has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)) {
$this->_uservisible = false;
}
}
return $this->_uservisible;
}
/**
* Restores the course_sections.sequence value
*
* @return string
*/
private function get_sequence() {
if (!empty($this->modinfo->sections[$this->_section])) {
return implode(',', $this->modinfo->sections[$this->_section]);
} else {
return '';
}
}
/**
* Returns course ID - from course_sections table
*
* @return int
*/
private function get_course() {
return $this->modinfo->get_course_id();
}
/**
+82
View File
@@ -35,6 +35,88 @@ require_once($CFG->libdir . '/conditionlib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_modinfolib_testcase extends advanced_testcase {
public function test_section_info_properties() {
global $DB, $CFG;
$this->resetAfterTest();
$oldcfgenableavailability = $CFG->enableavailability;
$oldcfgenablecompletion = $CFG->enablecompletion;
set_config('enableavailability', 1);
set_config('enablecompletion', 1);
$this->setAdminUser();
// Generate the course and pre-requisite module.
$course = $this->getDataGenerator()->create_course(
array('format' => 'topics',
'numsections' => 3,
'enablecompletion' => 1,
'groupmode' => SEPARATEGROUPS,
'forcegroupmode' => 0),
array('createsections' => true));
$coursecontext = context_course::instance($course->id);
$prereqforum = $this->getDataGenerator()->create_module('forum',
array('course' => $course->id),
array('completion' => 1));
// Generate the module and add availability conditions.
$conditionscompletion = array($prereqforum->cmid => COMPLETION_COMPLETE);
$conditionsgrade = array(666 => (object)array('min' => 0.4, 'max' => null, 'name' => '!missing'));
$conditionsfield = array('email' => (object)array(
'fieldname' => 'email',
'operator' => 'contains',
'value' => 'test'
));
$sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
$ci = new condition_info_section((object)array('id' => $sectiondb->id), CONDITION_MISSING_EVERYTHING);
foreach ($conditionscompletion as $cmid => $requiredcompletion) {
$ci->add_completion_condition($cmid, $requiredcompletion);
}
foreach ($conditionsgrade as $gradeid => $conditiongrade) {
$ci->add_grade_condition($gradeid, $conditiongrade->min, $conditiongrade->max, true);
}
foreach ($conditionsfield as $conditionfield) {
$ci->add_user_field_condition($conditionfield->fieldname, $conditionfield->operator, $conditionfield->value);
}
// Create and enrol a student.
$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
$student = $this->getDataGenerator()->create_user();
role_assign($studentrole->id, $student->id, $coursecontext);
$enrolplugin = enrol_get_plugin('manual');
$enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
$enrolplugin->enrol_user($enrolinstance, $student->id);
$this->setUser($student);
// Get modinfo.
$modinfo = get_fast_modinfo($course->id);
$si = $modinfo->get_section_info(2);
$this->assertEquals($sectiondb->id, $si->id);
$this->assertEquals($sectiondb->course, $si->course);
$this->assertEquals($sectiondb->section, $si->section);
$this->assertEquals($sectiondb->name, $si->name);
$this->assertEquals($sectiondb->visible, $si->visible);
$this->assertEquals($sectiondb->summary, $si->summary);
$this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
$this->assertEquals($sectiondb->showavailability, $si->showavailability);
$this->assertEquals($sectiondb->availablefrom, $si->availablefrom);
$this->assertEquals($sectiondb->availableuntil, $si->availableuntil);
$this->assertEquals($sectiondb->groupingid, $si->groupingid);
$this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
$this->assertEquals($conditionscompletion, $si->conditionscompletion);
$this->assertEquals($conditionsgrade, $si->conditionsgrade);
$this->assertEquals($conditionsfield, $si->conditionsfield);
// Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
$this->assertEquals(0, $si->available);
$this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
$this->assertEquals(0, $si->uservisible);
// Restore settings.
set_config('enableavailability', $oldcfgenableavailability);
set_config('enablecompletion', $oldcfgenablecompletion);
}
/**
* Test is_user_access_restricted_by_group()