Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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/>./*** Contains the base definition class for any course format plugin.** @package core_courseformat* @copyright 2020 Ferran Recio <ferran@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace core_courseformat;use navigation_node;use moodle_page;use cm_info;use core_component;use course_modinfo;use html_writer;use section_info;use context_course;use editsection_form;use moodle_exception;use coding_exception;use moodle_url;use lang_string;use core_external\external_api;use stdClass;use cache;use core_courseformat\output\legacy_renderer;/*** Base class for course formats** Each course format must declare class* class format_FORMATNAME extends core_courseformat\base {}* in file lib.php** For each course just one instance of this class is created and it will always be returned by* course_get_format($courseorid). Format may store it's specific course-dependent options in* variables of this class.** In rare cases instance of child class may be created just for format without course id* i.e. to check if format supports AJAX.** Also course formats may extend class section_info and overwrite* course_format::build_section_cache() to return more information about sections.** If you are upgrading from Moodle 2.3 start with copying the class format_legacy and renaming* it to format_FORMATNAME, then move the code from your callback functions into* appropriate functions of the class.** @package core_courseformat* @copyright 2012 Marina Glancy* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/abstract class base {/** @var int Id of the course in this instance (maybe 0) */protected $courseid;/** @var string format used for this course. Please note that it can be different from* course.format field if course referes to non-existing of disabled format */protected $format;/** @var stdClass course data for course object, please use course_format::get_course() */protected $course = false;/** @var array caches format options, please use course_format::get_format_options() */protected $formatoptions = array();/** @var int|null the section number in single section format, null for multiple section formats. */protected $singlesection = null;/** @var int|null the sectionid when a single section is selected, null when multiple sections are displayed. */protected $singlesectionid = null;/** @var course_modinfo the current course modinfo, please use course_format::get_modinfo() */private $modinfo = null;/** @var array cached instances */private static $instances = array();/** @var array plugin name => class name. */private static $classesforformat = array('site' => 'site');/** @var sectionmanager the format section manager. */protected $sectionmanager = null;/*** Creates a new instance of class** Please use course_get_format($courseorid) to get an instance of the format class** @param string $format* @param int $courseid*/protected function __construct($format, $courseid) {$this->format = $format;$this->courseid = $courseid;}/*** Validates that course format exists and enabled and returns either itself or default format** @param string $format* @return string*/final protected static function get_format_or_default($format) {global $CFG;require_once($CFG->dirroot . '/course/lib.php');if (array_key_exists($format, self::$classesforformat)) {return self::$classesforformat[$format];}$plugins = get_sorted_course_formats();foreach ($plugins as $plugin) {self::$classesforformat[$plugin] = $plugin;}if (array_key_exists($format, self::$classesforformat)) {return self::$classesforformat[$format];}if (PHPUNIT_TEST && class_exists('format_' . $format)) {// Allow unittests to use non-existing course formats.return $format;}// Else return default format.$defaultformat = get_config('moodlecourse', 'format');if (!in_array($defaultformat, $plugins)) {// When default format is not set correctly, use the first available format.$defaultformat = reset($plugins);}debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);self::$classesforformat[$format] = $defaultformat;return $defaultformat;}/*** Get class name for the format.** If course format xxx does not declare class format_xxx, format_legacy will be returned.* This function also includes lib.php file from corresponding format plugin** @param string $format* @return string*/final protected static function get_class_name($format) {global $CFG;static $classnames = array('site' => 'format_site');if (!isset($classnames[$format])) {$plugins = core_component::get_plugin_list('format');$usedformat = self::get_format_or_default($format);if (isset($plugins[$usedformat]) && file_exists($plugins[$usedformat].'/lib.php')) {require_once($plugins[$usedformat].'/lib.php');}$classnames[$format] = 'format_'. $usedformat;if (!class_exists($classnames[$format])) {require_once($CFG->dirroot.'/course/format/formatlegacy.php');$classnames[$format] = 'format_legacy';}}return $classnames[$format];}/*** Returns an instance of the class** @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances** @param int|stdClass $courseorid either course id or* an object that has the property 'format' and may contain property 'id'* @return base*/final public static function instance($courseorid) {global $DB;if (!is_object($courseorid)) {$courseid = (int)$courseorid;if ($courseid && isset(self::$instances[$courseid]) && count(self::$instances[$courseid]) == 1) {$formats = array_keys(self::$instances[$courseid]);$format = reset($formats);} else {$format = $DB->get_field('course', 'format', array('id' => $courseid), MUST_EXIST);}} else {$format = $courseorid->format;if (isset($courseorid->id)) {$courseid = clean_param($courseorid->id, PARAM_INT);} else {$courseid = 0;}}// Validate that format exists and enabled, use default otherwise.$format = self::get_format_or_default($format);if (!isset(self::$instances[$courseid][$format])) {$classname = self::get_class_name($format);self::$instances[$courseid][$format] = new $classname($format, $courseid);}return self::$instances[$courseid][$format];}/*** Resets cache for the course (or all caches)** To be called from rebuild_course_cache()** @param int $courseid*/final public static function reset_course_cache($courseid = 0) {if ($courseid) {if (isset(self::$instances[$courseid])) {foreach (self::$instances[$courseid] as $format => $object) {// In case somebody keeps the reference to course format object.self::$instances[$courseid][$format]->course = false;self::$instances[$courseid][$format]->formatoptions = array();self::$instances[$courseid][$format]->modinfo = null;}unset(self::$instances[$courseid]);}} else {self::$instances = array();}}/*** Reset the current user for all courses.** The course format cache resets every time the course cache resets but* also when the user changes their language, all course editors** @return void*/public static function session_cache_reset_all(): void {$statecache = cache::make('core', 'courseeditorstate');$statecache->purge();}/*** Reset the current user course format cache.** The course format cache resets every time the course cache resets but* also when the user changes their course format preference, complete* an activity...** @param stdClass $course the course object* @return string the new statekey*/public static function session_cache_reset(stdClass $course): string {$statecache = cache::make('core', 'courseeditorstate');$newkey = $course->cacherev . '_' . time();$statecache->set($course->id, $newkey);return $newkey;}/*** Return the current user course format cache key.** The course format session cache can be used to cache the* user course representation. The statekey will be reset when the* the course state changes. For example when the course is edited,* the user completes an activity or simply some course preference* like collapsing a section happens.** @param stdClass $course the course object* @return string the current statekey*/public static function session_cache(stdClass $course): string {$statecache = cache::make('core', 'courseeditorstate');$statekey = $statecache->get($course->id);// Validate the statekey code.if (preg_match('/^[0-9]+_[0-9]+$/', $statekey)) {list($cacherev) = explode('_', $statekey);if ($cacherev == $course->cacherev) {return $statekey;}}return self::session_cache_reset($course);}/*** Returns the format name used by this course** @return string*/final public function get_format() {return $this->format;}/*** Returns id of the course (0 if course is not specified)** @return int*/final public function get_courseid() {return $this->courseid;}/*** Returns the course context.** @return context_course the course context*/final public function get_context(): context_course {return context_course::instance($this->courseid);}/*** Returns a record from course database table plus additional fields* that course format defines** @return ?stdClass*/public function get_course() {global $DB;if (!$this->courseid) {return null;}if ($this->course === false) {$this->course = get_course($this->courseid);$options = $this->get_format_options();$dbcoursecolumns = null;foreach ($options as $optionname => $optionvalue) {if (isset($this->course->$optionname)) {// Course format options must not have the same names as existing columns in db table "course".if (!isset($dbcoursecolumns)) {$dbcoursecolumns = $DB->get_columns('course');}if (isset($dbcoursecolumns[$optionname])) {debugging('The option name '.$optionname.' in course format '.$this->format.' is invalid because the field with the same name exists in {course} table',DEBUG_DEVELOPER);continue;}}$this->course->$optionname = $optionvalue;}}return $this->course;}/*** Get the course display value for the current course.** Formats extending topics or weeks will use coursedisplay as this setting name* so they don't need to override the method. However, if the format uses a different* display logic it must override this method to ensure the core renderers know* if a COURSE_DISPLAY_MULTIPAGE or COURSE_DISPLAY_SINGLEPAGE is being used.** @return int The current value (COURSE_DISPLAY_MULTIPAGE or COURSE_DISPLAY_SINGLEPAGE)*/public function get_course_display(): int {return $this->get_course()->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE;}/*** Return the current course modinfo.** This method is used mainly by the output components to avoid unnecesary get_fast_modinfo calls.** @return course_modinfo*/public function get_modinfo(): course_modinfo {global $USER;if ($this->modinfo === null) {$this->modinfo = course_modinfo::instance($this->courseid, $USER->id);}return $this->modinfo;}/*** Method used in the rendered and during backup instead of legacy 'numsections'** Default renderer will treat sections with sectionnumber greater that the value returned by this* method as "orphaned" and not display them on the course page unless in editing mode.* Backup will store this value as 'numsections'.** This method ensures that 3rd party course format plugins that still use 'numsections' continue to* work but at the same time we no longer expect formats to have 'numsections' property.** @return int The last section number, or -1 if sections are entirely missing*/public function get_last_section_number() {$course = $this->get_course();if (isset($course->numsections)) {return $course->numsections;}$modinfo = get_fast_modinfo($course);$sections = $modinfo->get_section_info_all();// Sections seem to be missing entirely. Avoid subsequent errors and return early.if (count($sections) === 0) {return -1;}return (int)max(array_keys($sections));}/*** Method used to get the maximum number of sections for this course format.* @return int*/public function get_max_sections() {$maxsections = get_config('moodlecourse', 'maxsections');if (!isset($maxsections) || !is_numeric($maxsections)) {$maxsections = 52;}return $maxsections;}/*** Returns true if the course has a front page.** This function is called to determine if the course has a view page, whether or not* it contains a listing of activities. It can be useful to set this to false when the course* format has only one activity and ignores the course page. Or if there are multiple* activities but no page to see the centralised information.** Initially this was created to know if forms should add a button to return to the course page.* So if 'Return to course' does not make sense in your format your should probably return false.** @return boolean* @since Moodle 2.6*/public function has_view_page() {return true;}/*** Generate the title for this section page.** @return string the page title*/public function page_title(): string {global $PAGE;return $PAGE->title;}/*** Returns true if this course format uses sections** This function may be called without specifying the course id* i.e. in course_format_uses_sections()** Developers, note that if course format does use sections there should be defined a language* string with the name 'sectionname' defining what the section relates to in the format, i.e.* $string['sectionname'] = 'Topic';* or* $string['sectionname'] = 'Week';** @return bool*/public function uses_sections() {return false;}/*** Returns true if this course format uses course index** This function may be called without specifying the course id* i.e. in course_index_drawer()** @return bool*/public function uses_course_index() {return false;}/*** Returns true if this course format uses activity indentation.** @return bool if the course format uses indentation.*/public function uses_indentation(): bool {return true;}/*** Returns a list of sections used in the course** This is a shortcut to get_fast_modinfo()->get_section_info_all()* @see get_fast_modinfo()* @see course_modinfo::get_section_info_all()** @return array of section_info objects*/final public function get_sections() {if ($course = $this->get_course()) {$modinfo = get_fast_modinfo($course);return $modinfo->get_section_info_all();}return array();}/*** Returns information about section used in course** @param int|stdClass $section either section number (field course_section.section) or row from course_section table* @param int $strictness* @return ?section_info*/final public function get_section($section, $strictness = IGNORE_MISSING) {if (is_object($section)) {$sectionnum = $section->section;} else {$sectionnum = $section;}$sections = $this->get_sections();if (array_key_exists($sectionnum, $sections)) {return $sections[$sectionnum];}if ($strictness == MUST_EXIST) {throw new moodle_exception('sectionnotexist');}return null;}/*** Returns the display name of the given section that the course prefers.** @param int|stdClass|section_info $section Section object from database or just field course_sections.section* @return string Display name that the course format prefers, e.g. "Topic 2"*/public function get_section_name($section) {if (is_object($section)) {$sectionnum = $section->section;} else {$sectionnum = $section;}if (get_string_manager()->string_exists('sectionname', 'format_' . $this->format)) {return get_string('sectionname', 'format_' . $this->format) . ' ' . $sectionnum;}// Return an empty string if there's no available section name string for the given format.return '';}/*** Returns the default section using course_format's implementation of get_section_name.** @param int|stdClass $section Section object from database or just field course_sections section* @return string The default value for the section name based on the given course format.*/public function get_default_section_name($section) {return self::get_section_name($section);}/*** Returns the name for the highlighted section.** @return string The name for the highlighted section based on the given course format.*/public function get_section_highlighted_name(): string {return get_string('highlighted');}/*** Set if the current format instance will show multiple sections or an individual one.** Some formats has the hability to swith from one section to multiple sections per page.** @param int|null $sectionid null for all sections or a sectionid.*/public function set_sectionid(?int $sectionid): void {if ($sectionid === null) {$this->singlesection = null;$this->singlesectionid = null;return;}$modinfo = get_fast_modinfo($this->courseid);$sectioninfo = $modinfo->get_section_info_by_id($sectionid);if ($sectioninfo === null) {throw new coding_exception('Invalid sectionid: '. $sectionid);}$this->singlesection = $sectioninfo->section;$this->singlesectionid = $sectionid;}/*** Get if the current format instance will show multiple sections or an individual one.** Some formats has the hability to swith from one section to multiple sections per page,* output components will use this method to know if the current display is a single or* multiple sections.** @return int|null null for all sections or the sectionid.*/public function get_sectionid(): ?int {return $this->singlesectionid;}/*** Set if the current format instance will show multiple sections or an individual one.** Some formats has the hability to swith from one section to multiple sections per page.** @param int $singlesection zero for all sections or a section number* @deprecated Since 4.4. Use set_sectionnum instead.* @todo MDL-80116 This will be deleted in Moodle 4.8.*/public function set_section_number(int $singlesection): void {debugging('The method core_courseformat\base::set_section_number() has been deprecated, please use set_sectionnum() instead.',DEBUG_DEVELOPER);if ($singlesection === 0) {// Convert zero to null, to guarantee all the sections are displayed.$singlesection = null;}$this->set_sectionnum($singlesection);}/*** Set the current section number to display.* Some formats has the hability to swith from one section to multiple sections per page.** @since Moodle 4.4* @param int|null $sectionnum null for all sections or a sectionid.*/public function set_sectionnum(?int $sectionnum): void {if ($sectionnum === null) {$this->singlesection = null;$this->singlesectionid = null;return;}$modinfo = get_fast_modinfo($this->courseid);$sectioninfo = $modinfo->get_section_info($sectionnum);if ($sectioninfo === null) {throw new coding_exception('Invalid sectionnum: '. $sectionnum);}$this->singlesection = $sectionnum;$this->singlesectionid = $sectioninfo->id;}/*** Get the current section number to display.* Some formats has the hability to swith from one section to multiple sections per page.** @since Moodle 4.4* @return int|null the current section number or null when there is no single section.*/public function get_sectionnum(): ?int {return $this->singlesection;}/*** Set if the current format instance will show multiple sections or an individual one.** Some formats has the hability to swith from one section to multiple sections per page,* output components will use this method to know if the current display is a single or* multiple sections.** @return int zero for all sections or the sectin number* @deprecated Since 4.4. Use get_sectionnum instead.* @todo MDL-80116 This will be deleted in Moodle 4.8.*/public function get_section_number(): int {debugging('The method core_courseformat\base::get_section_number() has been deprecated, please use get_sectionnum() instead.',DEBUG_DEVELOPER);if ($this->singlesection === null) {// Convert null to zero, to guarantee all the sections are displayed.return 0;}return $this->singlesection;}/*** Return the format section preferences.** @return array of preferences indexed by sectionid*/public function get_sections_preferences(): array {global $USER;$result = [];$course = $this->get_course();$coursesectionscache = cache::make('core', 'coursesectionspreferences');$coursesections = $coursesectionscache->get($course->id);if ($coursesections) {return $coursesections;}$sectionpreferences = $this->get_sections_preferences_by_preference();foreach ($sectionpreferences as $preference => $sectionids) {if (!empty($sectionids) && is_array($sectionids)) {foreach ($sectionids as $sectionid) {if (!isset($result[$sectionid])) {$result[$sectionid] = new stdClass();}$result[$sectionid]->$preference = true;}}}$coursesectionscache->set($course->id, $result);return $result;}/*** Return the format section preferences.** @return array of preferences indexed by preference name*/public function get_sections_preferences_by_preference(): array {global $USER;$course = $this->get_course();try {$sectionpreferences = json_decode(get_user_preferences("coursesectionspreferences_{$course->id}", '', $USER->id),true,);if (empty($sectionpreferences)) {$sectionpreferences = [];}} catch (\Throwable $e) {$sectionpreferences = [];}return $sectionpreferences;}/*** Return the format section preferences.** @param string $preferencename preference name* @param int[] $sectionids affected section ids**/public function set_sections_preference(string $preferencename, array $sectionids) {$sectionpreferences = $this->get_sections_preferences_by_preference();$sectionpreferences[$preferencename] = $sectionids;$this->persist_to_user_preference($sectionpreferences);}/*** Add section preference ids.** @param string $preferencename preference name* @param array $sectionids affected section ids*/public function add_section_preference_ids(string $preferencename,array $sectionids,): void {$sectionpreferences = $this->get_sections_preferences_by_preference();if (!isset($sectionpreferences[$preferencename])) {$sectionpreferences[$preferencename] = [];}foreach ($sectionids as $sectionid) {if (!in_array($sectionid, $sectionpreferences[$preferencename])) {$sectionpreferences[$preferencename][] = $sectionid;}}$this->persist_to_user_preference($sectionpreferences);}/*** Remove section preference ids.** @param string $preferencename preference name* @param array $sectionids affected section ids*/public function remove_section_preference_ids(string $preferencename,array $sectionids,): void {$sectionpreferences = $this->get_sections_preferences_by_preference();if (!isset($sectionpreferences[$preferencename])) {$sectionpreferences[$preferencename] = [];}foreach ($sectionids as $sectionid) {if (($key = array_search($sectionid, $sectionpreferences[$preferencename])) !== false) {unset($sectionpreferences[$preferencename][$key]);}}$this->persist_to_user_preference($sectionpreferences);}/*** Persist the section preferences to the user preferences.** @param array $sectionpreferences the section preferences*/private function persist_to_user_preference(array $sectionpreferences,): void {global $USER;$course = $this->get_course();set_user_preference('coursesectionspreferences_' . $course->id, json_encode($sectionpreferences), $USER->id);// Invalidate section preferences cache.$coursesectionscache = cache::make('core', 'coursesectionspreferences');$coursesectionscache->delete($course->id);}/*** Returns the information about the ajax support in the given source format** The returned object's property (boolean)capable indicates that* the course format supports Moodle course ajax features.** @return stdClass*/public function supports_ajax() {// No support by default.$ajaxsupport = new stdClass();$ajaxsupport->capable = false;return $ajaxsupport;}/*** Returns true if this course format is compatible with content components.** Using components means the content elements can watch the frontend course state and* react to the changes. Formats with component compatibility can have more interactions* without refreshing the page, like having drag and drop from the course index to reorder* sections and activities.** @return bool if the format is compatible with components.*/public function supports_components() {return false;}/*** Custom action after section has been moved in AJAX mode** Used in course/rest.php** @return ?array This will be passed in ajax respose*/public function ajax_section_move() {return null;}/*** The URL to use for the specified course (with section)** Please note that course view page /course/view.php?id=COURSEID is hardcoded in many* places in core and contributed modules. If course format wants to change the location* of the view script, it is not enough to change just this function. Do not forget* to add proper redirection.** @param int|stdClass $section Section object from database or just field course_sections.section* if null the course view page is returned* @param array $options options for view URL. At the moment core uses:* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.* 'sr' (int) used by course formats to specify to which section to return* 'expanded' (bool) if true the section will be shown expanded, true by default* @return null|moodle_url*/public function get_view_url($section, $options = array()) {$course = $this->get_course();$url = new moodle_url('/course/view.php', ['id' => $course->id]);if (array_key_exists('sr', $options)) {$sectionno = $options['sr'];} else if (is_object($section)) {$sectionno = $section->section;} else {$sectionno = $section;}if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {// Display section on separate page.$sectioninfo = $this->get_section($sectionno);return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);}if ($this->uses_sections() && $sectionno !== null) {// The url includes the parameter to expand the section by default.if (!array_key_exists('expanded', $options)) {$options['expanded'] = true;}if ($options['expanded']) {// This parameter is being set by default.$url->param('expandsection', $sectionno);}$url->set_anchor('section-'.$sectionno);}return $url;}/*** Return the old non-ajax activity action url.** BrowserKit behats tests cannot trigger javascript events,* so we must translate to an old non-ajax url while non-ajax* course editing is still supported.** @param string $action action name the reactive action* @param cm_info $cm course module* @return moodle_url*/public function get_non_ajax_cm_action_url(string $action, cm_info $cm): moodle_url {$nonajaxactions = ['cmDelete' => 'delete','cmDuplicate' => 'duplicate','cmHide' => 'hide','cmShow' => 'show','cmStealth' => 'stealth',];if (!isset($nonajaxactions[$action])) {throw new coding_exception('Unknown activity action: ' . $action);}$nonajaxaction = $nonajaxactions[$action];$nonajaxurl = new moodle_url('/course/mod.php',['sesskey' => sesskey(), $nonajaxaction => $cm->id]);if (!is_null($this->get_sectionid())) {$nonajaxurl->param('sr', $this->get_sectionnum());}return $nonajaxurl;}/*** Loads all of the course sections into the navigation** This method is called from global_navigation::load_course_sections()** By default the method global_navigation::load_generic_course_sections() is called** When overwriting please note that navigationlib relies on using the correct values for* arguments $type and $key in navigation_node::add()** Example of code creating a section node:* $sectionnode = $node->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);* $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;** Example of code creating an activity node:* $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);* if (global_navigation::module_extends_navigation($activity->modname)) {* $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;* } else {* $activitynode->nodetype = navigation_node::NODETYPE_LEAF;* }** Also note that if $navigation->includesectionnum is not null, the section with this relative* number needs is expected to be loaded** @param \global_navigation $navigation* @param navigation_node $node The course node within the navigation*/public function extend_course_navigation($navigation, navigation_node $node) {if ($course = $this->get_course()) {$navigation->load_generic_course_sections($course, $node);}return array();}/*** Returns the list of blocks to be automatically added for the newly created course** @see blocks_add_default_course_blocks()** @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT* each of values is an array of block names (for left and right side columns)*/public function get_default_blocks() {global $CFG;if (isset($CFG->defaultblocks)) {return blocks_parse_default_blocks_list($CFG->defaultblocks);}$blocknames = array(BLOCK_POS_LEFT => array(),BLOCK_POS_RIGHT => array());return $blocknames;}/*** Return custom strings for the course editor.** This method is mainly used to translate the "section" related strings into* the specific format plugins name such as "Topics" or "Weeks".** @return stdClass[] an array of objects with string "component" and "key"*/public function get_editor_custom_strings(): array {$result = [];$stringmanager = get_string_manager();$component = 'format_' . $this->format;$formatoverridbles = ['sectionavailability_title','sectiondelete_title','sectiondelete_info','sectionsdelete_title','sectionsdelete_info','sectionmove_title','sectionmove_info','sectionsavailability_title','sectionsmove_title','sectionsmove_info','selectsection'];foreach ($formatoverridbles as $key) {if ($stringmanager->string_exists($key, $component)) {$result[] = (object)['component' => $component, 'key' => $key];}}return $result;}/*** Get the proper format plugin string if it exists.** If the format_PLUGINNAME does not provide a valid string,* core_courseformat will be user as the component.** @param string $key the string key* @param string|object|array|int $data extra data that can be used within translation strings* @return string the get_string result*/public function get_format_string(string $key, $data = null): string {$component = 'format_' . $this->get_format();if (!get_string_manager()->string_exists($key, $component)) {$component = 'core_courseformat';}return get_string($key, $component, $data);}/*** Returns the localised name of this course format plugin** @return lang_string*/final public function get_format_name() {return new lang_string('pluginname', 'format_'.$this->get_format());}/*** Definitions of the additional options that this course format uses for course** This function may be called often, it should be as fast as possible.* Avoid using get_string() method, use "new lang_string()" instead* It is not recommended to use dynamic or course-dependant expressions here* This function may be also called when course does not exist yet.** Option names must be different from fields in the {course} talbe or any form elements on* course edit form, it may even make sence to use special prefix for them.** Each option must have the option name as a key and the array of properties as a value:* 'default' - default value for this option (assumed null if not specified)* 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.)** Additional properties used by default implementation of* course_format::create_edit_form_elements() (calls this method with $foreditform = true)* 'label' - localised human-readable label for the edit form* 'element_type' - type of the form element, default 'text'* 'element_attributes' - additional attributes for the form element, these are 4th and further* arguments in the moodleform::addElement() method* 'help' - string for help button. Note that if 'help' value is 'myoption' then the string with* the name 'myoption_help' must exist in the language file* 'help_component' - language component to look for help string, by default this the component* for this course format** This is an interface for creating simple form elements. If format plugin wants to use other* methods such as disableIf, it can be done by overriding create_edit_form_elements().** Course format options can be accessed as:* $this->get_course()->OPTIONNAME (inside the format class)* course_get_format($course)->get_course()->OPTIONNAME (outside of format class)** All course options are returned by calling:* $this->get_format_options();** @param bool $foreditform* @return array of options*/public function course_format_options($foreditform = false) {return array();}/*** Definitions of the additional options that this course format uses for section** See course_format::course_format_options() for return array definition.** Additionally section format options may have property 'cache' set to true* if this option needs to be cached in get_fast_modinfo(). The 'cache' property* is recommended to be set only for fields used in course_format::get_section_name(),* course_format::extend_course_navigation() and course_format::get_view_url()** For better performance cached options are recommended to have 'cachedefault' property* Unlike 'default', 'cachedefault' should be static and not access get_config().** Regardless of value of 'cache' all options are accessed in the code as* $sectioninfo->OPTIONNAME* where $sectioninfo is instance of section_info, returned by* get_fast_modinfo($course)->get_section_info($sectionnum)* or get_fast_modinfo($course)->get_section_info_all()** All format options for particular section are returned by calling:* $this->get_format_options($section);** @param bool $foreditform* @return array*/public function section_format_options($foreditform = false) {return array();}/*** Returns the format options stored for this course or course section** When overriding please note that this function is called from rebuild_course_cache()* and section_info object, therefore using of get_fast_modinfo() and/or any function that* accesses it may lead to recursion.** @param null|int|stdClass|section_info $section if null the course format options will be returned* otherwise options for specified section will be returned. This can be either* section object or relative section number (field course_sections.section)* @return array*/public function get_format_options($section = null) {global $DB;if ($section === null) {$options = $this->course_format_options();} else {$options = $this->section_format_options();}if (empty($options)) {// There are no option for course/sections anyway, no need to go further.return array();}if ($section === null) {// Course format options will be returned.$sectionid = 0;} else if ($this->courseid && isset($section->id)) {// Course section format options will be returned.$sectionid = $section->id;} else if ($this->courseid && is_int($section) &&($sectionobj = $DB->get_record('course_sections',array('section' => $section, 'course' => $this->courseid), 'id'))) {// Course section format options will be returned.$sectionid = $sectionobj->id;} else {// Non-existing (yet) section was passed as an argument// default format options for course section will be returned.$sectionid = -1;}if (!array_key_exists($sectionid, $this->formatoptions)) {$this->formatoptions[$sectionid] = array();// First fill with default values.foreach ($options as $optionname => $optionparams) {$this->formatoptions[$sectionid][$optionname] = null;if (array_key_exists('default', $optionparams)) {$this->formatoptions[$sectionid][$optionname] = $optionparams['default'];}}if ($this->courseid && $sectionid !== -1) {// Overwrite the default options values with those stored in course_format_options table// nothing can be stored if we are interested in generic course ($this->courseid == 0)// or generic section ($sectionid === 0).$records = $DB->get_records('course_format_options',array('courseid' => $this->courseid,'format' => $this->format,'sectionid' => $sectionid), '', 'id,name,value');$indexedrecords = [];foreach ($records as $record) {$indexedrecords[$record->name] = $record->value;}foreach ($options as $optionname => $option) {contract_value($this->formatoptions[$sectionid], $indexedrecords, $option, $optionname);}}}return $this->formatoptions[$sectionid];}/*** Adds format options elements to the course/section edit form** This function is called from course_edit_form::definition_after_data()** @param \MoodleQuickForm $mform form the elements are added to* @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form* @return array array of references to the added form elements*/public function create_edit_form_elements(&$mform, $forsection = false) {$elements = array();if ($forsection) {$options = $this->section_format_options(true);} else {$options = $this->course_format_options(true);}foreach ($options as $optionname => $option) {if (!isset($option['element_type'])) {$option['element_type'] = 'text';}$args = array($option['element_type'], $optionname, $option['label']);if (!empty($option['element_attributes'])) {$args = array_merge($args, $option['element_attributes']);}$elements[] = call_user_func_array(array($mform, 'addElement'), $args);if (isset($option['help'])) {$helpcomponent = 'format_'. $this->get_format();if (isset($option['help_component'])) {$helpcomponent = $option['help_component'];}$mform->addHelpButton($optionname, $option['help'], $helpcomponent);}if (isset($option['type'])) {$mform->setType($optionname, $option['type']);}if (isset($option['default']) && !array_key_exists($optionname, $mform->_defaultValues)) {// Set defaults for the elements in the form.// Since we call this method after set_data() make sure that we don't override what was already set.$mform->setDefault($optionname, $option['default']);}}if (!$forsection && empty($this->courseid)) {// Check if course end date form field should be enabled by default.// If a default date is provided to the form element, it is magically enabled by default in the// MoodleQuickForm_date_time_selector class, otherwise it's disabled by default.if (get_config('moodlecourse', 'courseenddateenabled')) {// At this stage (this is called from definition_after_data) course data is already set as default.// We can not overwrite what is in the database.$mform->setDefault('enddate', $this->get_default_course_enddate($mform));}}return $elements;}/*** Override if you need to perform some extra validation of the format options** @param array $data array of ("fieldname"=>value) of submitted data* @param array $files array of uploaded files "element_name"=>tmp_file_path* @param array $errors errors already discovered in edit form validation* @return array of "element_name"=>"error_description" if there are errors,* or an empty array if everything is OK.* Do not repeat errors from $errors param here*/public function edit_form_validation($data, $files, $errors) {return array();}/*** Prepares values of course or section format options before storing them in DB** If an option has invalid value it is not returned** @param array $rawdata associative array of the proposed course/section format options* @param int|null $sectionid null if it is course format option* @return array array of options that have valid values*/protected function validate_format_options(array $rawdata, int $sectionid = null): array {if (!$sectionid) {$allformatoptions = $this->course_format_options(true);} else {$allformatoptions = $this->section_format_options(true);}$data = array_intersect_key($rawdata, $allformatoptions);foreach ($data as $key => $value) {$option = $allformatoptions[$key] + ['type' => PARAM_RAW, 'element_type' => null, 'element_attributes' => [[]]];expand_value($data, $data, $option, $key);if ($option['element_type'] === 'select' && !array_key_exists($data[$key], $option['element_attributes'][0])) {// Value invalid for select element, skip.unset($data[$key]);}}return $data;}/*** Validates format options for the course** @param array $data data to insert/update* @return array array of options that have valid values*/public function validate_course_format_options(array $data): array {return $this->validate_format_options($data);}/*** Updates format options for a course or section** If $data does not contain property with the option name, the option will not be updated** @param stdClass|array $data return value from moodleform::get_data() or array with data* @param null|int $sectionid null if these are options for course or section id (course_sections.id)* if these are options for section* @return bool whether there were any changes to the options values*/protected function update_format_options($data, $sectionid = null) {global $DB;$data = $this->validate_format_options((array)$data, $sectionid);if (!$sectionid) {$allformatoptions = $this->course_format_options();$sectionid = 0;} else {$allformatoptions = $this->section_format_options();}if (empty($allformatoptions)) {// Nothing to update anyway.return false;}$defaultoptions = array();$cached = array();foreach ($allformatoptions as $key => $option) {$defaultoptions[$key] = null;if (array_key_exists('default', $option)) {$defaultoptions[$key] = $option['default'];}expand_value($defaultoptions, $defaultoptions, $option, $key);$cached[$key] = ($sectionid === 0 || !empty($option['cache']));}$records = $DB->get_records('course_format_options',array('courseid' => $this->courseid,'format' => $this->format,'sectionid' => $sectionid), '', 'name,id,value');$changed = $needrebuild = false;foreach ($defaultoptions as $key => $value) {if (isset($records[$key])) {if (array_key_exists($key, $data) && $records[$key]->value != $data[$key]) {$DB->set_field('course_format_options', 'value',$data[$key], array('id' => $records[$key]->id));$changed = true;$needrebuild = $needrebuild || $cached[$key];}} else {if (array_key_exists($key, $data) && $data[$key] !== $value) {$newvalue = $data[$key];$changed = true;$needrebuild = $needrebuild || $cached[$key];} else {$newvalue = $value;// We still insert entry in DB but there are no changes from user point of// view and no need to call rebuild_course_cache().}$DB->insert_record('course_format_options', array('courseid' => $this->courseid,'format' => $this->format,'sectionid' => $sectionid,'name' => $key,'value' => $newvalue));}}if ($needrebuild) {if ($sectionid) {// Invalidate the section cache by given section id.course_modinfo::purge_course_section_cache_by_id($this->courseid, $sectionid);// Partial rebuild sections that have been invalidated.rebuild_course_cache($this->courseid, true, true);} else {// Full rebuild if sectionid is null.rebuild_course_cache($this->courseid);}}if ($changed) {// Reset internal caches.if (!$sectionid) {$this->course = false;}unset($this->formatoptions[$sectionid]);}return $changed;}/*** Updates format options for a course** If $data does not contain property with the option name, the option will not be updated** @param stdClass|array $data return value from moodleform::get_data() or array with data* @param stdClass $oldcourse if this function is called from update_course()* this object contains information about the course before update* @return bool whether there were any changes to the options values*/public function update_course_format_options($data, $oldcourse = null) {return $this->update_format_options($data);}/*** Updates format options for a section** Section id is expected in $data->id (or $data['id'])* If $data does not contain property with the option name, the option will not be updated** @param stdClass|array $data return value from moodleform::get_data() or array with data* @return bool whether there were any changes to the options values*/public function update_section_format_options($data) {$data = (array)$data;return $this->update_format_options($data, $data['id']);}/*** Return an instance of moodleform to edit a specified section** Default implementation returns instance of editsection_form that automatically adds* additional fields defined in course_format::section_format_options()** Format plugins may extend editsection_form if they want to have custom edit section form.** @param mixed $action the action attribute for the form. If empty defaults to auto detect the* current url. If a moodle_url object then outputs params as hidden variables.* @param array $customdata the array with custom data to be passed to the form* /course/editsection.php passes section_info object in 'cs' field* for filling availability fields* @return \moodleform*/public function editsection_form($action, $customdata = array()) {global $CFG;require_once($CFG->dirroot. '/course/editsection_form.php');if (!array_key_exists('course', $customdata)) {$customdata['course'] = $this->get_course();}return new editsection_form($action, $customdata);}/*** Allows course format to execute code on moodle_page::set_course()** @param moodle_page $page instance of page calling set_course*/public function page_set_course(moodle_page $page) {}/*** Allows course format to execute code on moodle_page::set_cm()** Current module can be accessed as $page->cm (returns instance of cm_info)** @param moodle_page $page instance of page calling set_cm*/public function page_set_cm(moodle_page $page) {}/*** Course-specific information to be output on any course page (usually above navigation bar)** Example of usage:* define* class format_FORMATNAME_XXX implements renderable {}** create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:* class format_FORMATNAME_renderer extends plugin_renderer_base {* protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {* return html_writer::tag('div', 'This is my header/footer');* }* }** Return instance of format_FORMATNAME_XXX in this function, the appropriate method from* plugin renderer will be called** @return null|\renderable null for no output or object with data for plugin renderer*/public function course_header() {return null;}/*** Course-specific information to be output on any course page (usually in the beginning of* standard footer)** See course_format::course_header() for usage** @return null|\renderable null for no output or object with data for plugin renderer*/public function course_footer() {return null;}/*** Course-specific information to be output immediately above content on any course page** See course_format::course_header() for usage** @return null|\renderable null for no output or object with data for plugin renderer*/public function course_content_header() {return null;}/*** Course-specific information to be output immediately below content on any course page** See course_format::course_header() for usage** @return null|\renderable null for no output or object with data for plugin renderer*/public function course_content_footer() {return null;}/*** Returns instance of page renderer used by this plugin** @param moodle_page $page* @return \renderer_base*/public function get_renderer(moodle_page $page) {try {$renderer = $page->get_renderer('format_'. $this->get_format());} catch (moodle_exception $e) {$formatname = $this->get_format();$expectedrenderername = 'format_'. $this->get_format() . '\output\renderer';debugging("The '{$formatname}' course format does not define the {$expectedrenderername} renderer class. This is required since Moodle 4.0.",DEBUG_DEVELOPER);$renderer = new legacy_renderer($page, null);}return $renderer;}/*** Returns instance of output component used by this plugin** @throws coding_exception if the format class does not extends the original core one.* @param string $outputname the element to render (section, activity...)* @return string the output component classname*/public function get_output_classname(string $outputname): string {// The core output class.$baseclass = "core_courseformat\\output\\local\\$outputname";// Look in this format and any parent formats before we get to the base one.$classes = array_merge([get_class($this)], class_parents($this));foreach ($classes as $component) {if ($component === self::class) {break;}// Because course formats are in the root namespace, there is no need to process the// class name - it is already a Frankenstyle component name beginning 'format_'.// Check if there is a specific class in this format.$outputclass = "$component\\output\\courseformat\\$outputname";if (class_exists($outputclass)) {// Check that the outputclass is a subclass of the base class.if (!is_subclass_of($outputclass, $baseclass)) {throw new coding_exception("The \"$outputclass\" must extend \"$baseclass\"");}return $outputclass;}}return $baseclass;}/*** Returns true if the specified section is current** By default we analyze $course->marker** @param int|stdClass|section_info $section* @return bool*/public function is_section_current($section) {if (is_object($section)) {$sectionnum = $section->section;} else {$sectionnum = $section;}return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);}/*** Returns if an specific section is visible to the current user.** Formats can overrride this method to implement any special section logic.** @param section_info $section the section modinfo* @return bool;*/public function is_section_visible(section_info $section): bool {// Previous to Moodle 4.0 thas logic was hardcoded. To prevent errors in the contrib plugins// the default logic is the same required for topics and weeks format and still uses// a "hiddensections" format setting.$course = $this->get_course();$hidesections = $course->hiddensections ?? true;// Show the section if the user is permitted to access it, OR if it's not available// but there is some available info text which explains the reason & should display,// OR it is hidden but the course has a setting to display hidden sections as unavailable.return $section->uservisible ||($section->visible && !$section->available && !empty($section->availableinfo)) ||(!$section->visible && !$hidesections);}/*** return true if the course editor must be displayed.** @param array|null $capabilities array of capabilities a user needs to have to see edit controls in general.* If null or not specified, the user needs to have 'moodle/course:manageactivities'.* @return bool true if edit controls must be displayed*/public function show_editor(?array $capabilities = ['moodle/course:manageactivities']): bool {global $PAGE;$course = $this->get_course();$coursecontext = context_course::instance($course->id);if ($capabilities === null) {$capabilities = ['moodle/course:manageactivities'];}return $PAGE->user_is_editing() && has_all_capabilities($capabilities, $coursecontext);}/*** Check if the group mode can be displayed.* @param cm_info $cm the activity module* @return bool*/public function show_groupmode(cm_info $cm): bool {if (!plugin_supports('mod', $cm->modname, FEATURE_GROUPS, false)) {return false;}if (!has_capability('moodle/course:manageactivities', $cm->context)) {return false;}return true;}/*** Allows to specify for modinfo that section is not available even when it is visible and conditionally available.** Note: affected user can be retrieved as: $section->modinfo->userid** Course format plugins can override the method to change the properties $available and $availableinfo that were* calculated by conditional availability.* To make section unavailable set:* $available = false;* To make unavailable section completely hidden set:* $availableinfo = '';* To make unavailable section visible with availability message set:* $availableinfo = get_string('sectionhidden', 'format_xxx');** @param section_info $section* @param bool $available the 'available' propery of the section_info as it was evaluated by conditional availability.* Can be changed by the method but 'false' can not be overridden by 'true'.* @param string $availableinfo the 'availableinfo' propery of the section_info as it was evaluated by conditional availability.* Can be changed by the method*/public function section_get_available_hook(section_info $section, &$available, &$availableinfo) {}/*** Whether this format allows to delete sections** If format supports deleting sections it is also recommended to define language string* 'deletesection' inside the format.** Do not call this function directly, instead use course_can_delete_section()** @param int|stdClass|section_info $section* @return bool*/public function can_delete_section($section) {return false;}/*** Deletes a section** Do not call this function directly, instead call course_delete_section()** @param int|stdClass|section_info $sectionornum* @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.* @return bool whether section was deleted*/public function delete_section($sectionornum, $forcedeleteifnotempty = false) {global $DB;if (!$this->uses_sections()) {// Not possible to delete section if sections are not used.return false;}if (is_object($sectionornum)) {$section = $sectionornum;} else {$section = $DB->get_record('course_sections',['course' => $this->get_courseid(), 'section' => $sectionornum],'id,section,sequence,summary');}if (!$section || !$section->section) {// Not possible to delete 0-section.return false;}if (!$forcedeleteifnotempty && (!empty($section->sequence) || !empty($section->summary))) {return false;}$course = $this->get_course();// Remove the marker if it points to this section.if ($section->section == $course->marker) {course_set_marker($course->id, 0);}$lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections}WHERE course = ?', array($course->id));// Find out if we need to descrease the 'numsections' property later.$courseformathasnumsections = array_key_exists('numsections',$this->get_format_options());$decreasenumsections = $courseformathasnumsections && ($section->section <= $course->numsections);// Move the section to the end.move_section_to($course, $section->section, $lastsection, true);// Delete all modules from the section.foreach (preg_split('/,/', $section->sequence, -1, PREG_SPLIT_NO_EMPTY) as $cmid) {course_delete_module($cmid);}// Delete section and it's format options.$DB->delete_records('course_format_options', array('sectionid' => $section->id));$DB->delete_records('course_sections', array('id' => $section->id));// Invalidate the section cache by given section id.course_modinfo::purge_course_section_cache_by_id($course->id, $section->id);// Partial rebuild section cache that has been purged.rebuild_course_cache($course->id, true, true);// Delete section summary files.$context = \context_course::instance($course->id);$fs = get_file_storage();$fs->delete_area_files($context->id, 'course', 'section', $section->id);// Descrease 'numsections' if needed.if ($decreasenumsections) {$this->update_course_format_options(array('numsections' => $course->numsections - 1));}return true;}/*** Wrapper for course_delete_module method.** Format plugins can override this method to provide their own implementation of course_delete_module.** @param cm_info $cm the course module information* @param bool $async whether or not to try to delete the module using an adhoc task. Async also depends on a plugin hook.* @throws moodle_exception*/public function delete_module(cm_info $cm, bool $async = false) {course_delete_module($cm->id, $async);}/*** Moves a section just after the target section.** @param section_info $section the section to move* @param section_info $destination the section that should be below the moved section* @return boolean if the section can be moved or not*/public function move_section_after(section_info $section, section_info $destination): bool {if ($section->section == $destination->section || $section->section == $destination->section + 1) {return true;}// The move_section_to moves relative to the section to move. However, this// method will move the target section always after the destination.if ($section->section > $destination->section) {$newsectionnumber = $destination->section + 1;} else {$newsectionnumber = $destination->section;}return move_section_to($this->get_course(),$section->section,$newsectionnumber);}/*** Prepares the templateable object to display section name** @param \section_info|\stdClass $section* @param bool $linkifneeded* @param bool $editable* @param null|lang_string|string $edithint* @param null|lang_string|string $editlabel* @return \core\output\inplace_editable*/public function inplace_editable_render_section_name($section, $linkifneeded = true,$editable = null, $edithint = null, $editlabel = null) {global $USER, $CFG;require_once($CFG->dirroot.'/course/lib.php');if ($editable === null) {$editable = !empty($USER->editing) && has_capability('moodle/course:update',context_course::instance($section->course));}$displayvalue = $title = get_section_name($section->course, $section);if ($linkifneeded) {// Display link under the section name if the course format setting is to display one section per page.$url = course_get_url($section->course, $section->section, array('navigation' => true));if ($url) {$displayvalue = html_writer::link($url, $title);}$itemtype = 'sectionname';} else {// If $linkifneeded==false, we never display the link (this is used when rendering the section header).// Itemtype 'sectionnamenl' (nl=no link) will tell the callback that link should not be rendered -// there is no other way callback can know where we display the section name.$itemtype = 'sectionnamenl';}if (empty($edithint)) {$edithint = new lang_string('editsectionname');}if (empty($editlabel)) {$editlabel = new lang_string('newsectionname', '', $title);}return new \core\output\inplace_editable('format_' . $this->format, $itemtype, $section->id, $editable,$displayvalue, $section->name, $edithint, $editlabel);}/*** Updates the value in the database and modifies this object respectively.** ALWAYS check user permissions before performing an update! Throw exceptions if permissions are not sufficient* or value is not legit.** @param stdClass $section* @param string $itemtype* @param mixed $newvalue* @return ?\core\output\inplace_editable*/public function inplace_editable_update_section_name($section, $itemtype, $newvalue) {if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {$context = context_course::instance($section->course);external_api::validate_context($context);require_capability('moodle/course:update', $context);$newtitle = clean_param($newvalue, PARAM_TEXT);if (strval($section->name) !== strval($newtitle)) {course_update_section($section->course, $section, array('name' => $newtitle));}return $this->inplace_editable_render_section_name($section, ($itemtype === 'sectionname'), true);}}/*** Returns the default end date value based on the start date.** This is the default implementation for course formats, it is based on* moodlecourse/courseduration setting. Course formats like format_weeks for* example can overwrite this method and return a value based on their internal options.** @param \MoodleQuickForm $mform* @param array $fieldnames The form - field names mapping.* @return int*/public function get_default_course_enddate($mform, $fieldnames = array()) {if (empty($fieldnames)) {$fieldnames = array('startdate' => 'startdate');}$startdate = $this->get_form_start_date($mform, $fieldnames);$courseduration = intval(get_config('moodlecourse', 'courseduration'));if (!$courseduration) {// Default, it should be already set during upgrade though.$courseduration = YEARSECS;}return $startdate + $courseduration;}/*** Indicates whether the course format supports the creation of the Announcements forum.** For course format plugin developers, please override this to return true if you want the Announcements forum* to be created upon course creation.** @return bool*/public function supports_news() {// For backwards compatibility, check if default blocks include the news_items block.$defaultblocks = $this->get_default_blocks();foreach ($defaultblocks as $blocks) {if (in_array('news_items', $blocks)) {return true;}}// Return false by default.return false;}/*** Get the start date value from the course settings page form.** @param \MoodleQuickForm $mform* @param array $fieldnames The form - field names mapping.* @return int*/protected function get_form_start_date($mform, $fieldnames) {$startdate = $mform->getElementValue($fieldnames['startdate']);return $mform->getElement($fieldnames['startdate'])->exportValue($startdate);}/*** Returns whether this course format allows the activity to* have "triple visibility state" - visible always, hidden on course page but available, hidden.** @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)* @param stdClass|section_info $section section where this module is located or will be added to* @return bool*/public function allow_stealth_module_visibility($cm, $section) {return false;}/*** Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide)** Access to the course is already validated in the WS but the callback has to make sure* that particular action is allowed by checking capabilities** Course formats should register** @param stdClass|section_info $section* @param string $action* @param int $sr the section return* @return null|array|stdClass any data for the Javascript post-processor (must be json-encodeable)*/public function section_action($section, $action, $sr) {global $PAGE;if (!$this->uses_sections() || !$section->section) {// No section actions are allowed if course format does not support sections.// No actions are allowed on the 0-section by default (overwrite in course format if needed).throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));}$course = $this->get_course();$coursecontext = context_course::instance($course->id);$modinfo = $this->get_modinfo();$renderer = $this->get_renderer($PAGE);if (!($section instanceof section_info)) {$section = $modinfo->get_section_info($section->section);}if (!is_null($sr)) {$this->set_sectionnum($sr);}switch($action) {case 'hide':case 'show':require_capability('moodle/course:sectionvisibility', $coursecontext);$visible = ($action === 'hide') ? 0 : 1;course_update_section($course, $section, array('visible' => $visible));break;case 'refresh':return ['content' => $renderer->course_section_updated($this, $section),];default:throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));}return ['modules' => $this->get_section_modules_updated($section)];}/*** Return an array with all section modules content.** This method is used in section_action method to generate the updated modules content* after a modinfo change.** @param section_info $section the section* @return string[] the full modules content.*/protected function get_section_modules_updated(section_info $section): array {global $PAGE;$modules = [];if (!$this->uses_sections() || !$section->section) {return $modules;}// Load the cmlist output from the updated modinfo.$renderer = $this->get_renderer($PAGE);$modinfo = $this->get_modinfo();$coursesections = $modinfo->sections;if (array_key_exists($section->section, $coursesections)) {foreach ($coursesections[$section->section] as $cmid) {$cm = $modinfo->get_cm($cmid);$modules[] = $renderer->course_section_updated_cm_item($this, $section, $cm);}}return $modules;}/*** Return the plugin config settings for external functions,* in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.** @return array the list of configs* @since Moodle 3.5*/public function get_config_for_external() {return array();}/*** Course deletion hook.** Format plugins can override this method to clean any format specific data and dependencies.**/public function delete_format_data() {global $DB;$course = $this->get_course();// By default, formats store some most display specifics in a user preference.$DB->delete_records('user_preferences', ['name' => 'coursesectionspreferences_' . $course->id]);}/*** Duplicate a section** @param section_info $originalsection The section to be duplicated* @return section_info The new duplicated section* @since Moodle 4.2*/public function duplicate_section(section_info $originalsection): section_info {if (!$this->uses_sections()) {throw new moodle_exception('sectionsnotsupported', 'core_courseformat');}$course = $this->get_course();$context = context_course::instance($course->id);$newsection = course_create_section($course, $originalsection->section + 1); // Place new section after existing one.$newsectiondata = new stdClass();if (!empty($originalsection->name)) {$newsectiondata->name = get_string('duplicatedsection', 'moodle', $originalsection->name);} else {$newsectiondata->name = $originalsection->name;}$newsectiondata->summary = $originalsection->summary;$newsectiondata->summaryformat = $originalsection->summaryformat;$newsectiondata->visible = $originalsection->visible;$newsectiondata->availability = $originalsection->availability;foreach ($this->section_format_options() as $key => $value) {$newsectiondata->$key = $originalsection->$key;}course_update_section($course, $newsection, $newsectiondata);try {$fs = get_file_storage();$files = $fs->get_area_files($context->id, 'course', 'section', $originalsection->id);foreach ($files as $f) {$fileinfo = ['contextid' => $context->id,'component' => 'course','filearea' => 'section','itemid' => $newsection->id,];$fs->create_file_from_storedfile($fileinfo, $f);}} catch (\Exception $e) {debugging('Error copying section files.' . $e->getMessage(), DEBUG_DEVELOPER);}$modinfo = $this->get_modinfo();// Duplicate the section modules, should they exist.if (array_key_exists($originalsection->section, $modinfo->sections)) {foreach ($modinfo->sections[$originalsection->section] as $modnumber) {$originalcm = $modinfo->cms[$modnumber];if (!$originalcm->deletioninprogress) {duplicate_module($course, $originalcm, $newsection->id, false);}}}return get_fast_modinfo($course)->get_section_info_by_id($newsection->id);}/*** Get the required javascript files for the course format.** @return array The list of javascript files required by the course format.*/public function get_required_jsfiles(): array {return [];}/*** Determines whether section items can be removed from the navigation, just like the breadcrumb feature seen on activity pages.* By default, it returns false but can be overridden by the course format to change the behaviour.** @return bool True if sections can be removed, false otherwise.*/public function can_sections_be_removed_from_navigation(): bool {return false;}}