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:
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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'])) {
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user