MDL-86155 course: Move course internals to autoloading

| Old class name    | New class name           |
| ---               | ---                      |
| `\cm_info`        | `\course\cm_info
| `\cached_cm_info` | `\course\cached_cm_info` |
| `\section_info`   | `\course\section_info`   |
| `\course_modinfo` | `\course\modinfo`        |
This commit is contained in:
Andrew Nicols
2025-08-06 20:03:37 +08:00
parent 20bb0b2429
commit 70a4e2a3ba
12 changed files with 5658 additions and 5434 deletions
@@ -0,0 +1,14 @@
issueNumber: MDL-86155
notes:
core_course:
- message: |
- The following classes have been renamed and now support autoloading.
Existing classes are currently unaffected.
| Old class name | New class name |
| --- | --- |
| `\cm_info` | `\course\cm_info
| `\cached_cm_info` | `\course\cached_cm_info` |
| `\section_info` | `\course\section_info` |
| `\course_modinfo` | `\course\modinfo` |
type: improved
+95
View File
@@ -0,0 +1,95 @@
<?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/>.
namespace core_course;
/**
* Class that is the return value for the _get_coursemodule_info module API function.
*
* Note: For backward compatibility, you can also return a stdclass object from that function.
* The difference is that the stdclass object may contain an 'extra' field (deprecated,
* use extraclasses and onclick instead). The stdclass object may not contain
* the new fields defined here (content, extraclasses, customdata).
*
* @package core_course
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Sam Marshall
*/
class cached_cm_info {
/**
* Name (text of link) for this activity; Leave unset to accept default name
* @var string
*/
public $name;
/**
* Name of icon for this activity. Normally, this should be used together with $iconcomponent
* to define the icon, as per image_url function.
* For backward compatibility, if this value is of the form 'mod/forum/icon' then an icon
* within that module will be used.
* @see cm_info::get_icon_url()
* @see \core\output\renderer_base::image_url()
* @var string
*/
public $icon;
/**
* Component for icon for this activity, as per image_url; leave blank to use default 'moodle'
* component
* @see \core\output\renderer_base::image_url()
* @var string
*/
public $iconcomponent;
/**
* HTML content to be displayed on the main page below the link (if any) for this course-module
* @var string
*/
public $content;
/**
* Custom data to be stored in modinfo for this activity; useful if there are cases when
* internal information for this activity type needs to be accessible from elsewhere on the
* course without making database queries. May be of any type but should be short.
* @var mixed
*/
public $customdata;
/**
* Extra CSS class or classes to be added when this activity is displayed on the main page;
* space-separated string
* @var string
*/
public $extraclasses;
/**
* External URL image to be used by activity as icon, useful for some external-tool modules
* like lti. If set, takes precedence over $icon and $iconcomponent
* @var $moodle_url
*/
public $iconurl;
/**
* Content of onclick JavaScript; escaped HTML to be inserted as attribute value
* @var string
*/
public $onclick;
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(cached_cm_info::class, \cached_cm_info::class);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+651
View File
@@ -0,0 +1,651 @@
<?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/>.
namespace core_course;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
use core\context\course as context_course;
use core_courseformat\sectiondelegate;
use core_courseformat\sectiondelegatemodule;
/**
* Data about a single section on a course.
*
* This contains the fields from the.course_sections table, plus additional data when required.
*
* @package core_course
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Sam Marshall
* @property-read int $id Section ID - from course_sections table
* @property-read int $course Course ID - from course_sections table
* @property-read int $sectionnum 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 string $availability Availability information as JSON string - from course_sections table
* @property-read string|null $component Optional section delegate component - from course_sections table
* @property-read int|null $itemid Optional section delegate item id - 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.
* @property-read course_modinfo $modinfo
*/
class section_info implements IteratorAggregate {
/**
* Section ID - from course_sections table
* @var int
*/
private $_id;
/**
* Section number - from course_sections table
* @var int
*/
private $_sectionnum;
/**
* Section name if specified - from course_sections table
* @var string
*/
private $_name;
/**
* Section visibility (1 = visible) - from course_sections table
* @var int
*/
private $_visible;
/**
* Section summary text if specified - from course_sections table
* @var string
*/
private $_summary;
/**
* Section summary text format (FORMAT_xx constant) - from course_sections table
* @var int
*/
private $_summaryformat;
/**
* Availability information as JSON string - from course_sections table
* @var string
*/
private $_availability;
/**
* @var string|null the delegated component if any.
*/
private ?string $_component = null;
/**
* @var int|null the delegated instance item id if any.
*/
private ?int $_itemid = null;
/**
* @var sectiondelegate|null Section delegate instance if any.
*/
private ?sectiondelegate $_delegateinstance = null;
/** @var cm_info[]|null Section cm_info activities, null when it is not loaded yet. */
private array|null $_sequencecminfos = null;
/**
* @var bool|null $_isorphan True if the section is orphan for some reason.
*/
private $_isorphan = null;
/**
* 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
* @var array
*/
private $_conditionscompletion;
/**
* 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
* @var array
*/
private $_conditionsgrade;
/**
* Availability conditions for this section based on user fields
* @var array
*/
private $_conditionsfield;
/**
* True if this section is available to students i.e. if all availability conditions
* are met - obtained dynamically on request, see function {@link section_info::get_available()}
* @var bool|null
*/
private $_available;
/**
* 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 on request, see
* function {@link section_info::get_availableinfo()}
* @var string
*/
private $_availableinfo;
/**
* 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) - obtained dynamically
* on request, see function {@link section_info::get_uservisible()}
* @var bool|null
*/
private $_uservisible;
/**
* Default values for sectioncache fields; if a field has this value, it won't
* be stored in the sectioncache cache, to save space. Checks are done by ===
* which means values must all be strings.
* @var array
*/
private static $sectioncachedefaults = array(
'name' => null,
'summary' => '',
'summaryformat' => '1', // FORMAT_HTML, but must be a string
'visible' => '1',
'availability' => null,
'component' => null,
'itemid' => null,
);
/**
* Stores format options that have been cached when building 'coursecache'
* When the format option is requested we look first if it has been cached
* @var array
*/
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;
/**
* True if has activities, otherwise false.
* @var bool
*/
public $hasactivites;
/**
* List of class read-only properties' getter methods.
* Used by magic functions __get(), __isset(), __empty()
* @var array
*/
private static $standardproperties = [
'section' => 'get_section_number',
];
/**
* 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 mixed $notused1 argument not used (informaion is available in $modinfo)
* @param mixed $notused2 argument not used (informaion is available in $modinfo)
* @param modinfo $modinfo Owner (needed for checking availability)
* @param mixed $notused3 argument not used (informaion is available in $modinfo)
*/
public function __construct($data, $number, $notused1, $notused2, $modinfo, $notused3) {
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
// Data that is always present
$this->_id = $data->id;
$defaults = self::$sectioncachedefaults +
array('conditionscompletion' => array(),
'conditionsgrade' => array(),
'conditionsfield' => array());
// Data that may use default values to save cache size
foreach ($defaults as $field => $value) {
if (isset($data->{$field})) {
$this->{'_'.$field} = $data->{$field};
} else {
$this->{'_'.$field} = $value;
}
}
// Other data from constructor arguments.
$this->_sectionnum = $number;
$this->modinfo = $modinfo;
// Cached course format data.
$course = $modinfo->get_course();
if (!isset(self::$sectionformatoptions[$course->format])) {
// Store list of section format options defined in each used course format.
// They do not depend on particular course but only on its format.
self::$sectionformatoptions[$course->format] =
course_get_format($course)->section_format_options();
}
foreach (self::$sectionformatoptions[$course->format] as $field => $option) {
if (!empty($option['cache'])) {
if (isset($data->{$field})) {
$this->cachedformatoptions[$field] = $data->{$field};
} else if (array_key_exists('cachedefault', $option)) {
$this->cachedformatoptions[$field] = $option['cachedefault'];
}
}
}
}
/**
* Magic method to check if the property is set
*
* @param string $name name of the property
* @return bool
*/
public function __isset($name) {
if (isset(self::$standardproperties[$name])) {
$value = $this->__get($name);
return isset($value);
}
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);
}
return false;
}
/**
* Magic method to check if the property is empty
*
* @param string $name name of the property
* @return bool
*/
public function __empty($name) {
if (isset(self::$standardproperties[$name])) {
$value = $this->__get($name);
return empty($value);
}
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 empty($value);
}
return true;
}
/**
* Magic method to retrieve the property, this is either basic section property
* or availability information or additional properties added by course format
*
* @param string $name name of the property
* @return mixed
*/
public function __get($name) {
if (isset(self::$standardproperties[$name])) {
if ($method = self::$standardproperties[$name]) {
return $this->$method();
}
}
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];
}
// precheck if the option is defined in format to avoid unnecessary DB queries in get_format_options()
if (array_key_exists($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, but can be called directly if there
* is a case when it might be called recursively (you can't call property values recursively).
*
* @return bool
*/
public function get_available() {
global $CFG;
$userid = $this->modinfo->get_user_id();
if ($this->_available !== null || $userid == -1) {
// Has already been calculated or does not need calculation.
return $this->_available;
}
$this->_available = true;
$this->_availableinfo = '';
if (!empty($CFG->enableavailability)) {
// Get availability information.
$ci = new \core_availability\info_section($this);
$this->_available = $ci->is_available($this->_availableinfo, true,
$userid, $this->modinfo);
}
if ($this->_available) {
$this->_available = $this->check_delegated_available();
}
// Execute the hook from the course format that may override the available/availableinfo properties.
$currentavailable = $this->_available;
course_get_format($this->modinfo->get_course())->
section_get_available_hook($this, $this->_available, $this->_availableinfo);
if (!$currentavailable && $this->_available) {
debugging('section_get_available_hook() can not make unavailable section available', DEBUG_DEVELOPER);
$this->_available = $currentavailable;
}
return $this->_available;
}
/**
* Check if the delegated component is available.
*
* @return bool
*/
private function check_delegated_available(): bool {
/** @var sectiondelegatemodule $sectiondelegate */
$sectiondelegate = $this->get_component_instance();
if (!$sectiondelegate) {
return true;
}
if ($sectiondelegate instanceof sectiondelegatemodule) {
$parentcm = $sectiondelegate->get_cm();
if (!$parentcm->available) {
return false;
}
return $parentcm->get_section_info()->available;
}
return true;
}
/**
* Returns the availability text shown next to the section on course page.
*
* @return string
*/
private function get_availableinfo() {
// Calling get_available() will also fill the availableinfo property
// (or leave it null if there is no userid).
$this->get_available();
return $this->_availableinfo;
}
/**
* Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
* and use {@link convert_to_array()}
*
* @return ArrayIterator
*/
public function getIterator(): Traversable {
$ret = array();
foreach (get_object_vars($this) as $key => $value) {
if (substr($key, 0, 1) == '_') {
if (method_exists($this, 'get'.$key)) {
$ret[substr($key, 1)] = $this->{'get'.$key}();
} else {
$ret[substr($key, 1)] = $this->$key;
}
}
}
$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));
return new ArrayIterator($ret);
}
/**
* Works out whether activity is visible *for current user* - if this is false, they
* aren't allowed to access it.
*
* @return bool
*/
private function get_uservisible() {
$userid = $this->modinfo->get_user_id();
if ($this->_uservisible !== null || $userid == -1) {
// Has already been calculated or does not need calculation.
return $this->_uservisible;
}
if (!$this->check_delegated_uservisible()) {
$this->_uservisible = false;
return $this->_uservisible;
}
$this->_uservisible = true;
if ($this->is_orphan() || !$this->_visible || !$this->get_available()) {
$coursecontext = context_course::instance($this->get_course());
if (
($this->_isorphan || !$this->_visible)
&& !has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)
) {
$this->_uservisible = false;
}
if (
$this->_uservisible
&& !$this->get_available()
&& !has_capability('moodle/course:ignoreavailabilityrestrictions', $coursecontext, $userid)
) {
$this->_uservisible = false;
}
}
return $this->_uservisible;
}
/**
* Check if the delegated component is user visible.
*
* @return bool
*/
private function check_delegated_uservisible(): bool {
/** @var sectiondelegatemodule $sectiondelegate */
$sectiondelegate = $this->get_component_instance();
if (!$sectiondelegate) {
return true;
}
if ($sectiondelegate instanceof sectiondelegatemodule) {
$parentcm = $sectiondelegate->get_cm();
if (!$parentcm->uservisible) {
return false;
}
$result = $parentcm->get_section_info()->uservisible;
return $result;
}
return true;
}
/**
* Restores the course_sections.sequence value
*
* @return string
*/
private function get_sequence() {
if (!empty($this->modinfo->sections[$this->_sectionnum])) {
return implode(',', $this->modinfo->sections[$this->_sectionnum]);
} else {
return '';
}
}
/**
* Returns the course modules in this section.
*
* @return cm_info[]
*/
public function get_sequence_cm_infos(): array {
if ($this->_sequencecminfos !== null) {
return $this->_sequencecminfos;
}
$sequence = $this->modinfo->sections[$this->_sectionnum] ?? [];
$cms = $this->modinfo->get_cms();
$result = [];
foreach ($sequence as $cmid) {
if (isset($cms[$cmid])) {
$result[] = $cms[$cmid];
}
}
$this->_sequencecminfos = $result;
return $result;
}
/**
* Returns course ID - from course_sections table
*
* @return int
*/
private function get_course() {
return $this->modinfo->get_course_id();
}
/**
* Modinfo object
*
* @return course_modinfo
*/
private function get_modinfo() {
return $this->modinfo;
}
/**
* Returns section number.
*
* This method is called by the property ->section.
*
* @return int
*/
private function get_section_number(): int {
return $this->sectionnum;
}
/**
* Get the delegate component instance.
*/
public function get_component_instance(): ?sectiondelegate {
if (!$this->is_delegated()) {
return null;
}
if ($this->_delegateinstance !== null) {
return $this->_delegateinstance;
}
$this->_delegateinstance = sectiondelegate::instance($this);
return $this->_delegateinstance;
}
/**
* Returns true if this section is a delegate to a component.
* @return bool
*/
public function is_delegated(): bool {
return !empty($this->_component);
}
/**
* Returns true if this section is orphan.
*
* @return bool
*/
public function is_orphan(): bool {
if ($this->_isorphan !== null) {
return $this->_isorphan;
}
$courseformat = course_get_format($this->modinfo->get_course());
// There are some cases where a restored course using third-party formats can
// have orphaned sections due to a fixed section number.
if ($this->_sectionnum > $courseformat->get_last_section_number()) {
$this->_isorphan = true;
return $this->_isorphan;
}
// Some delegated sections can belong to a plugin that is disabled or not present.
if ($this->is_delegated() && !$this->get_component_instance()) {
$this->_isorphan = true;
return $this->_isorphan;
}
$this->_isorphan = false;
return $this->_isorphan;
}
/**
* Prepares section data for inclusion in sectioncache cache, removing items
* that are set to defaults, and adding availability data if required.
*
* Called by build_section_cache in course_modinfo only; do not use otherwise.
* @param object $section Raw section data object
*/
public static function convert_for_section_cache($section) {
global $CFG;
// Course id stored in course table
unset($section->course);
// Sequence stored implicity in modinfo $sections array
unset($section->sequence);
// Remove default data
foreach (self::$sectioncachedefaults as $field => $value) {
// Exact compare as strings to avoid problems if some strings are set
// to "0" etc.
if (isset($section->{$field}) && $section->{$field} === $value) {
unset($section->{$field});
}
}
}
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(section_info::class, \section_info::class);
+351
View File
@@ -0,0 +1,351 @@
<?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/>.
namespace core_course;
use core\context\course as context_course;
use core\context\module as context_module;
use core\url;
/**
* Tests for
*
* @package core
* @category test
* @copyright 2025 Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\PHPUnit\Framework\Attributes\CoversClass(cm_info::class)]
final class cm_info_test extends \advanced_testcase {
public function test_cm_info_properties(): void {
global $DB, $CFG;
$this->resetAfterTest();
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 module and add availability conditions.
$availability = '{"op":"&","showc":[true,true,true],"c":[' .
'{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
COMPLETION_COMPLETE . '"},' .
'{"type":"grade","id":666,"min":0.4},' .
'{"type":"profile","op":"contains","sf":"email","v":"test"}' .
']}';
$assign = $this->getDataGenerator()->create_module('assign',
array('course' => $course->id),
array('idnumber' => 123,
'groupmode' => VISIBLEGROUPS,
'availability' => $availability));
rebuild_course_cache($course->id, true);
// Retrieve all related records from DB.
$assigndb = $DB->get_record('assign', array('id' => $assign->id));
$moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
$moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
$sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
$modnamessingular = get_module_types_names(false);
$modnamesplural = get_module_types_names(true);
// 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);
// Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
$rawmods = get_course_mods($course->id);
$cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
// Get modinfo.
$modinfo = get_fast_modinfo($course->id);
$cm = $modinfo->instances['assign'][$assign->id];
$this->assertEquals($moduledb->id, $cm->id);
$this->assertEquals($assigndb->id, $cm->instance);
$this->assertEquals($moduledb->course, $cm->course);
$this->assertEquals($moduledb->idnumber, $cm->idnumber);
$this->assertEquals($moduledb->added, $cm->added);
$this->assertEquals($moduledb->visible, $cm->visible);
$this->assertEquals($moduledb->visibleold, $cm->visibleold);
$this->assertEquals($moduledb->groupmode, $cm->groupmode);
$this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
$this->assertEquals($moduledb->groupingid, $cm->groupingid);
$this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
$this->assertEquals($course->groupmode, $cm->coursegroupmode);
$this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
$this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
$cm->effectivegroupmode); // (since mod_assign supports groups).
$this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
$this->assertEquals($moduledb->indent, $cm->indent);
$this->assertEquals($moduledb->completion, $cm->completion);
$this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
$this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
$this->assertEquals($moduledb->completionview, $cm->completionview);
$this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
$this->assertEquals($moduledb->showdescription, $cm->showdescription);
$this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
$this->assertEquals($cachedcminfo->icon, $cm->icon);
$this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
$this->assertEquals('assign', $cm->modname);
$this->assertEquals($moduledb->module, $cm->module);
$this->assertEquals($cachedcminfo->name, $cm->name);
$this->assertEquals($sectiondb->section, $cm->sectionnum);
$this->assertEquals($moduledb->section, $cm->section);
$this->assertEquals($availability, $cm->availability);
$this->assertEquals(context_module::instance($moduledb->id), $cm->context);
$this->assertEquals($modnamessingular['assign'], $cm->modfullname);
$this->assertEquals($modnamesplural['assign'], $cm->modplural);
$this->assertEquals(new url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
$this->assertEquals($cachedcminfo->customdata, $cm->customdata);
// Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
$this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
$this->assertEquals(0, $cm->uservisible);
$this->assertEquals('', $cm->extraclasses);
$this->assertEquals('', $cm->onclick);
$this->assertEquals(null, $cm->afterlink);
$this->assertEquals(null, $cm->afterediticons);
$this->assertEquals('', $cm->content);
// Attempt to access and set non-existing field.
$this->assertTrue(empty($modinfo->somefield));
$this->assertFalse(isset($modinfo->somefield));
$cm->somefield;
$this->assertDebuggingCalled();
$cm->somefield = 'Some value';
$this->assertDebuggingCalled();
$this->assertEmpty($cm->somefield);
$this->assertDebuggingCalled();
// Attempt to overwrite an existing field.
$prevvalue = $cm->name;
$this->assertNotEmpty($cm->name);
$this->assertFalse(empty($cm->name));
$this->assertTrue(isset($cm->name));
$cm->name = 'Illegal overwriting';
$this->assertDebuggingCalled();
$this->assertEquals($prevvalue, $cm->name);
$this->assertDebuggingNotCalled();
}
/**
* Tests for function cm_info::get_course_module_record()
*/
public function test_cm_info_get_course_module_record(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
set_config('enableavailability', 1);
set_config('enablecompletion', 1);
$course = $this->getDataGenerator()->create_course(
array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
array('createsections' => true));
$mods = array();
$mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
$mods[1] = $this->getDataGenerator()->create_module('assign',
array('course' => $course->id,
'section' => 3,
'idnumber' => '12345',
'showdescription' => true
));
// Pick a small valid availability value to use.
$availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
$mods[2] = $this->getDataGenerator()->create_module('book',
array('course' => $course->id,
'indent' => 5,
'availability' => $availabilityvalue,
'showdescription' => false,
'completion' => true,
'completionview' => true,
'completionexpected' => time() + 5000,
));
$mods[3] = $this->getDataGenerator()->create_module('forum',
array('course' => $course->id,
'visible' => 0,
'groupmode' => 1,
'availability' => null));
$mods[4] = $this->getDataGenerator()->create_module('forum',
array('course' => $course->id,
'grouping' => 12));
$modinfo = get_fast_modinfo($course->id);
// Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
$dbfields = array_keys($DB->get_columns('course_modules'));
sort($dbfields);
$cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
$cmrecordfields = array_keys((array)$cmrecord);
sort($cmrecordfields);
$this->assertEquals($dbfields, $cmrecordfields);
// Make sure that object returned by get_course_module_record(true) has exactly the same fields
// as object returned by get_coursemodule_from_id(,,,true,);
$cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
$cmrecordfullfields = array_keys((array)$cmrecordfull);
$cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
$cmfields = array_keys((array)$cm);
$this->assertEquals($cmfields, $cmrecordfullfields);
// Make sure that object returned by get_course_module_record(true) has exactly the same fields
// as object returned by get_coursemodule_from_instance(,,,true,);
$cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
$cmfields = array_keys((array)$cm);
$this->assertEquals($cmfields, $cmrecordfullfields);
// Make sure the objects have the same properties.
$cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
$cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
$cminfo = $modinfo->get_cm($mods[0]->cmid);
$record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
$this->assertEquals($record, $cminfo->get_course_module_record());
$this->assertEquals($cm1, $cminfo->get_course_module_record(true));
$this->assertEquals($cm2, $cminfo->get_course_module_record(true));
$cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
$cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
$cminfo = $modinfo->get_cm($mods[1]->cmid);
$record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
$this->assertEquals($record, $cminfo->get_course_module_record());
$this->assertEquals($cm1, $cminfo->get_course_module_record(true));
$this->assertEquals($cm2, $cminfo->get_course_module_record(true));
$cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
$cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
$cminfo = $modinfo->get_cm($mods[2]->cmid);
$record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
$this->assertEquals($record, $cminfo->get_course_module_record());
$this->assertEquals($cm1, $cminfo->get_course_module_record(true));
$this->assertEquals($cm2, $cminfo->get_course_module_record(true));
$cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
$cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
$cminfo = $modinfo->get_cm($mods[3]->cmid);
$record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
$this->assertEquals($record, $cminfo->get_course_module_record());
$this->assertEquals($cm1, $cminfo->get_course_module_record(true));
$this->assertEquals($cm2, $cminfo->get_course_module_record(true));
$cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
$cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
$cminfo = $modinfo->get_cm($mods[4]->cmid);
$record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
$this->assertEquals($record, $cminfo->get_course_module_record());
$this->assertEquals($cm1, $cminfo->get_course_module_record(true));
$this->assertEquals($cm2, $cminfo->get_course_module_record(true));
}
/**
* Tests for function cm_info::get_activitybadge().
*/
public function test_cm_info_get_activitybadge(): void {
global $PAGE;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
$label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
$renderer = $PAGE->get_renderer('core');
$modinfo = get_fast_modinfo($course->id);
// Forum and resource implements the activitybadge feature.
$cminfo = $modinfo->get_cm($forum->cmid);
$this->assertNotNull($cminfo->get_activitybadge($renderer));
$cminfo = $modinfo->get_cm($resource->cmid);
$this->assertNotNull($cminfo->get_activitybadge($renderer));
// Assign and label don't implement the activitybadge feature (at least for now).
$cminfo = $modinfo->get_cm($assign->cmid);
$this->assertNull($cminfo->get_activitybadge($renderer));
$cminfo = $modinfo->get_cm($label->cmid);
$this->assertNull($cminfo->get_activitybadge($renderer));
}
/**
* Test get_sections_delegated_by_cm method.
*/
public function test_get_delegated_section_info(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
// Add a section delegated by a course module.
$subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course]);
$otheractivity = $this->getDataGenerator()->create_module('page', ['course' => $course]);
$modinfo = get_fast_modinfo($course);
$delegatedsections = $modinfo->get_sections_delegated_by_cm();
$delegated = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
$this->assertNotNull($delegated);
$this->assertEquals($delegated, $delegatedsections[$subsection->cmid]);
$delegated = $modinfo->get_cm($otheractivity->cmid)->get_delegated_section_info();
$this->assertNull($delegated);
}
/**
* Test for cm_info::get_instance_record.
*/
public function test_section_get_instance_record(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 2]);
$activity = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 0]);
$modinfo = get_fast_modinfo($course->id);
$cminfo = $modinfo->get_cm($activity->cmid);
$instancerecord = $DB->get_record('page', ['id' => $activity->id]);
$instance = $cminfo->get_instance_record();
$this->assertEquals($instancerecord, $instance);
// The instance record should be cached.
$DB->delete_records('page', ['id' => $activity->id]);
$instance2 = $cminfo->get_instance_record();
$this->assertEquals($instancerecord, $instance);
$this->assertEquals($instance, $instance2);
}
}
File diff suppressed because it is too large Load Diff
+529
View File
@@ -0,0 +1,529 @@
<?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/>.
namespace core_course;
use core\context\course as context_course;
use core\plugin_manager;
use core_courseformat\formatactions;
/**
* Tests for \core_course\section_info.
*
* @package core
* @category test
* @copyright Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\PHPunit\Framework\Attributes\CoversClass(\core\section_info::class)]
final class section_info_test extends \advanced_testcase {
public function test_section_info_properties(): void {
global $DB, $CFG;
$this->resetAfterTest();
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));
// Add availability conditions.
$availability = '{"op":"&","showc":[true,true,true],"c":[' .
'{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
COMPLETION_COMPLETE . '"},' .
'{"type":"grade","id":666,"min":0.4},' .
'{"type":"profile","op":"contains","sf":"email","v":"test"}' .
']}';
$DB->set_field('course_sections', 'availability', $availability,
array('course' => $course->id, 'section' => 2));
rebuild_course_cache($course->id, true);
$sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
// 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->sequence, $si->sequence); // Since this section does not contain invalid modules.
$this->assertEquals($availability, $si->availability);
// 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);
}
/**
* Test for get_component_instance.
*/
public function test_get_component_instance(): void {
global $DB;
$this->resetAfterTest();
$this->load_fixture('core', 'sectiondelegatetest.php');
$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 2]);
course_update_section(
$course,
$DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]),
[
'component' => 'test_component',
'itemid' => 1,
]
);
$modinfo = get_fast_modinfo($course->id);
$sectioninfos = $modinfo->get_section_info_all();
$this->assertNull($sectioninfos[1]->get_component_instance());
$this->assertNull($sectioninfos[1]->component);
$this->assertNull($sectioninfos[1]->itemid);
$this->assertInstanceOf(\core_courseformat\sectiondelegate::class, $sectioninfos[2]->get_component_instance());
$this->assertInstanceOf(\test_component\courseformat\sectiondelegate::class, $sectioninfos[2]->get_component_instance());
$this->assertEquals('test_component', $sectioninfos[2]->component);
$this->assertEquals(1, $sectioninfos[2]->itemid);
}
/**
* Test for section_info is_delegated.
*/
public function test_is_delegated(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);
formatactions::section($course)->create_delegated('mod_label', 0);
$modinfo = get_fast_modinfo($course->id);
$sectioninfos = $modinfo->get_section_info_all();
$this->assertFalse($sectioninfos[1]->is_delegated());
$this->assertTrue($sectioninfos[2]->is_delegated());
}
/**
* Test get_uservisible method when the section is delegated.
*
* @dataProvider data_provider_get_uservisible_delegate
* @param string $role The role to assign to the user.
* @param bool $parentvisible The visibility of the parent section.
* @param bool $delegatedvisible The visibility of the delegated section.
* @param bool $expected The expected visibility of the delegated section.
*/
public function test_get_uservisible_delegate(
string $role,
bool $parentvisible,
bool $delegatedvisible,
bool $expected,
): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
$subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
$student = $this->getDataGenerator()->create_and_enrol($course, $role);
$modinfo = get_fast_modinfo($course);
formatactions::section($course)->update(
$modinfo->get_section_info(1),
['visible' => $parentvisible]
);
formatactions::cm($course)->set_visibility(
$subsection->cmid,
$delegatedvisible,
);
$this->setUser($student);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
// The get_uservisible is a magic getter.
$this->assertEquals($expected, $delegatedsection->uservisible);
}
/**
* Data provider for test_get_uservisible_delegate.
*
* @return array
*/
public static function data_provider_get_uservisible_delegate(): array {
return [
'Student on a visible subsection inside a visible parent' => [
'role' => 'student',
'parentvisible' => true,
'delegatedvisible' => true,
'expected' => true,
],
'Student on a hidden subsection inside a visible parent' => [
'role' => 'student',
'parentvisible' => true,
'delegatedvisible' => false,
'expected' => false,
],
'Student on a visible subsection inside a hidden parent' => [
'role' => 'student',
'parentvisible' => false,
'delegatedvisible' => true,
'expected' => false,
],
'Student on a hidden subsection inside a hidden parent' => [
'role' => 'student',
'parentvisible' => false,
'delegatedvisible' => false,
'expected' => false,
],
'Teacher on a visible subsection inside a visible parent' => [
'role' => 'editingteacher',
'parentvisible' => true,
'delegatedvisible' => true,
'expected' => true,
],
'Teacher on a hidden subsection inside a visible parent' => [
'role' => 'editingteacher',
'parentvisible' => true,
'delegatedvisible' => false,
'expected' => true,
],
'Teacher on a visible subsection inside a hidden parent' => [
'role' => 'editingteacher',
'parentvisible' => false,
'delegatedvisible' => true,
'expected' => true,
],
'Teacher on a hidden subsection inside a hidden parent' => [
'role' => 'editingteacher',
'parentvisible' => false,
'delegatedvisible' => false,
'expected' => true,
],
];
}
/**
* Test get_uservisible method when the section is delegated and depending on if the plugin is enabled.
*
* @dataProvider provider_test_get_uservisible_delegate_enabled
* @param string $role The role to assign to the user.
* @param bool $enabled Whether the plugin is enabled.
* @param bool $expected The expected visibility of the delegated section.
*/
public function test_get_uservisible_delegate_enabled(
string $role,
bool $enabled,
bool $expected,
): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
$subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
$user = $this->getDataGenerator()->create_and_enrol($course, $role);
if (!$enabled) {
$manager = plugin_manager::resolve_plugininfo_class('mod');
$manager::enable_plugin('subsection', 0);
rebuild_course_cache($course->id, true);
}
$this->setUser($user);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_section_info($delegatedsection->section);
// The get_uservisible is a magic getter.
$this->assertEquals($expected, $delegatedsection->uservisible);
}
/**
* Data provider for test_get_uservisible_delegate_enabled.
*
* @return array
*/
public static function provider_test_get_uservisible_delegate_enabled(): array {
return [
'Student with plugin enabled' => [
'role' => 'student',
'enabled' => true,
'expected' => true,
],
'Student with plugin disabled' => [
'role' => 'student',
'enabled' => false,
'expected' => false,
],
'Teacher with plugin enabled' => [
'role' => 'editingteacher',
'enabled' => true,
'expected' => true,
],
'Teacher with plugin disabled' => [
'role' => 'editingteacher',
'enabled' => false,
'expected' => true,
],
];
}
/**
* Test get_available method when the section is delegated.
*
* @dataProvider data_provider_get_available_delegated
* @param string $role The role to assign to the user.
* @param bool $parentavailable The parent section is available.
* @param bool $delegatedavailable The delegated section is available..
* @param bool $expectedavailable The expected availability of the delegated section.
* @param bool $expecteduservisible The expected uservisibility of the delegated section.
*/
public function test_get_available_delegated(
string $role,
bool $parentavailable,
bool $delegatedavailable,
bool $expectedavailable,
bool $expecteduservisible,
): void {
$this->resetAfterTest();
// The element will be available tomorrow.
$availability = json_encode(
(object) [
'op' => '&',
'showc' => [true],
'c' => [
[
'type' => 'date',
'd' => '>=',
't' => time() + DAYSECS,
],
],
]
);
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
$cmparams = ['section' => 1];
if (!$delegatedavailable) {
$cmparams['availability'] = $availability;
}
$subsection = $this->getDataGenerator()->create_module(
'subsection',
['course' => $course],
$cmparams
);
$student = $this->getDataGenerator()->create_and_enrol($course, $role);
$modinfo = get_fast_modinfo($course);
if (!$parentavailable) {
formatactions::section($course)->update(
$modinfo->get_section_info(1),
['availability' => $availability]
);
}
$this->setUser($student);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
// All section_info getters are magic methods.
$this->assertEquals($expectedavailable, $delegatedsection->available);
$this->assertEquals($expecteduservisible, $delegatedsection->uservisible);
}
/**
* Data provider for test_get_available_delegated.
*
* @return array
*/
public static function data_provider_get_available_delegated(): array {
return [
'Student on an available subsection inside an available parent' => [
'role' => 'student',
'parentavailable' => true,
'delegatedavailable' => true,
'expectedavailable' => true,
'expecteduservisible' => true,
],
'Student on an unavailable subsection inside an available parent' => [
'role' => 'student',
'parentavailable' => true,
'delegatedavailable' => false,
'expectedavailable' => false,
'expecteduservisible' => false,
],
'Student on an available subsection inside an unavailable parent' => [
'role' => 'student',
'parentavailable' => false,
'delegatedavailable' => true,
'expectedavailable' => false,
'expecteduservisible' => false,
],
'Student on an unavailable subsection inside an unavailable parent' => [
'role' => 'student',
'parentavailable' => false,
'delegatedavailable' => false,
'expectedavailable' => false,
'expecteduservisible' => false,
],
'Teacher on an available subsection inside an available parent' => [
'role' => 'editingteacher',
'parentavailable' => true,
'delegatedavailable' => true,
'expectedavailable' => true,
'expecteduservisible' => true,
],
'Teacher on an unavailable subsection inside an available parent' => [
'role' => 'editingteacher',
'parentavailable' => true,
'delegatedavailable' => false,
'expectedavailable' => false,
'expecteduservisible' => true,
],
'Teacher on an available subsection inside an unavailable parent' => [
'role' => 'editingteacher',
'parentavailable' => false,
'delegatedavailable' => true,
'expectedavailable' => false,
'expecteduservisible' => true,
],
'Teacher on an unavailable subsection inside an unavailable parent' => [
'role' => 'editingteacher',
'parentavailable' => false,
'delegatedavailable' => false,
'expectedavailable' => false,
'expecteduservisible' => true,
],
];
}
/**
* Test when a section is considered orphan.
*/
public function test_is_orphan(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
$subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
// If mod_subsection is enabled, a subsection is not orphan.
$modinfo = get_fast_modinfo($course);
$this->assertFalse($delegatedsection->is_orphan());
// Delegated sections without a component instance (disabled mod_subsection) is considered orphan.
$manager = plugin_manager::resolve_plugininfo_class('mod');
$manager::enable_plugin('subsection', 0);
rebuild_course_cache($course->id, true);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_section_info($delegatedsection->section);
$this->assertTrue($delegatedsection->is_orphan());
// Check enabling the plugin restore the previous state.
$manager::enable_plugin('subsection', 1);
rebuild_course_cache($course->id, true);
$modinfo = get_fast_modinfo($course);
$delegatedsection = $modinfo->get_section_info($delegatedsection->section);
$this->assertFalse($delegatedsection->is_orphan());
// Force section limit in the course format instance.
rebuild_course_cache($course->id, true);
$modinfo = get_fast_modinfo($course);
// Core formats does not use numsections anymore. We need to use reflection to change the value.
$format = course_get_format($course);
// Add a fake numsections format data (Force loading format data first).
$format->get_course();
$reflection = new \ReflectionObject($format);
$property = $reflection->getProperty('course');
$courseobject = $property->getValue($format);
$courseobject->numsections = 1;
$property->setValue($format, $courseobject);
$delegatedsection = $modinfo->get_section_info($delegatedsection->section);
$this->assertTrue($delegatedsection->is_orphan());
}
/**
* Test for section_info::get_sequence_cm_infos.ma
*/
public function test_section_get_sequence_cm_infos(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 2]);
$cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 0]);
$cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
$cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
$cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
$modinfo = get_fast_modinfo($course->id);
$sectioninfo = $modinfo->get_section_info(0);
$cms = $sectioninfo->get_sequence_cm_infos();
$this->assertCount(1, $cms);
$this->assertEquals($cm1->cmid, $cms[0]->id);
$sectioninfo = $modinfo->get_section_info(1);
$cms = $sectioninfo->get_sequence_cm_infos();
$this->assertCount(3, $cms);
$this->assertEquals($cm2->cmid, $cms[0]->id);
$this->assertEquals($cm3->cmid, $cms[1]->id);
$this->assertEquals($cm4->cmid, $cms[2]->id);
$sectioninfo = $modinfo->get_section_info(2);
$cms = $sectioninfo->get_sequence_cm_infos();
$this->assertCount(0, $cms);
}
}
+1 -2
View File
@@ -548,8 +548,7 @@ class component {
// Always keep moodle_exception in place.
$keyclasses = [
\core\exception\moodle_exception::class,
\core\output\bootstrap_renderer::class,
\core_cache\cache::class,
\core_course\section_info::class,
];
foreach ($keyclasses as $classname) {
if (!array_key_exists($classname, $cache['classmap'])) {
+17
View File
@@ -322,4 +322,21 @@ $legacyclasses = [
'core_filters',
'form/local_settings_form.php',
],
\course_modinfo::class => [
'core_course',
'modinfo.php',
],
\cm_info::class => [
'core_course',
'cm_info.php',
],
\cached_cm_info::class => [
'core_course',
'cached_cm_info.php',
],
\section_info::class => [
'core_course',
'section_info.php',
],
];
+10 -3421
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff