Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?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/>.

/**
 * Base class for course format plugins
 *
 * @package    core_course
 * @copyright  2012 Marina Glancy
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die;

> use core_courseformat\base as course_format; /** > use core_courseformat\output\site_renderer; * Returns an instance of format class (extending format_base) for given course >
< * Returns an instance of format class (extending format_base) for given course
> * Returns an instance of format class (extending course_format) for given course
* @param int|stdClass $courseorid either course id or * an object that has the property 'format' and may contain property 'id'
< * @return format_base
> * @return course_format
*/ function course_get_format($courseorid) {
< return format_base::instance($courseorid);
> return course_format::instance($courseorid);
} /**
< * Base class for course formats < * < * Each course format must declare class < * class format_FORMATNAME extends format_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 < * format_base::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.
> * Pseudo course format used for the site main page
* * @package core_course * @copyright 2012 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */
< abstract class format_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 data for course object, please use {@link format_base::get_course()} */ < protected $course = false; < /** @var array caches format options, please use {@link format_base::get_format_options()} */ < protected $formatoptions = array(); < /** @var array cached instances */ < private static $instances = array(); < /** @var array plugin name => class name. */ < private static $classesforformat = array('site' => 'site'); < < /** < * Creates a new instance of class < * < * Please use {@link course_get_format($courseorid)} to get an instance of the format class < * < * @param string $format < * @param int $courseid < * @return format_base < */ < 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 < */ < protected static final 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 < */ < protected static final 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 format_base < */ < public static final 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 {@link rebuild_course_cache()} < * < * @param int $courseid < */ < public static final 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(); < } < unset(self::$instances[$courseid]); < } < } else { < self::$instances = array(); < } < } < < /** < * Returns the format name used by this course < * < * @return string < */ < public final function get_format() { < return $this->format; < } < < /** < * Returns id of the course (0 if course is not specified) < * < * @return int < */ < public final function get_courseid() { < return $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; < } < < /** < * 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 < */ < 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(); < 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; < } < < /** < * Returns true if this course format uses sections < * < * This function may be called without specifying the course id < * i.e. in {@link 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 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 < */ < public final 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 < */ < public final 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; < }
> class format_site extends course_format {
/** * Returns the display name of the given section that the course prefers. *
< * @param int|stdClass $section Section object from database or just field course_sections.section
> * @param int|stdClass $section Section object from database or just field section.section
* @return Display name that the course format prefers, e.g. "Topic 2" */
< public function get_section_name($section) { < if (is_object($section)) { < $sectionnum = $section->section;
> function get_section_name($section) { > $section = $this->get_section($section); > if ((string)$section->name !== '') { > // Return the name the user set. > return format_string($section->name, true, array('context' => context_course::instance($this->courseid)));
} 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 format_base'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 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;
> return get_string('site');
}
< < /** < * 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.
> * For this fake course referring to the whole site, the site homepage is always returned > * regardless of arguments
*
< * @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 has no separate page, the function returns null < * 'sr' (int) used by multipage formats to specify to which section to return
> * @param int|stdClass $section > * @param array $options
* @return null|moodle_url */ public function get_view_url($section, $options = array()) {
< global $CFG; < $course = $this->get_course(); < $url = new moodle_url('/course/view.php', array('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($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) { < // by default assume that sections are never displayed on separate pages < return null; < } < if ($this->uses_sections() && $sectionno !== null) { < $url->set_anchor('section-'.$sectionno); < } < return $url; < } < < /** < * Loads all of the course sections into the navigation < * < * This method is called from {@link global_navigation::load_course_sections()} < * < * By default the method {@link 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 {@link 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();
> return new moodle_url('/', array('redirect' => 0));
} /**
< * Returns the list of blocks to be automatically added for the newly created course < * < * @see blocks_add_default_course_blocks()
> * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed
* * @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; < } < < /** < * Returns the localised name of this course format plugin < * < * @return lang_string < */ < public final function get_format_name() { < return new lang_string('pluginname', 'format_'.$this->get_format());
> return blocks_get_default_site_course_blocks();
} /**
< * 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 < * {@link format_base::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();
> * Definitions of the additional options that site uses
* * @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 {@link format_base::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 {@link get_fast_modinfo()}. The 'cache' property < * is recommended to be set only for fields used in {@link format_base::get_section_name()}, < * {@link format_base::extend_course_navigation()} and {@link format_base::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'); < foreach ($records as $record) { < if (array_key_exists($record->name, $this->formatoptions[$sectionid])) { < $value = $record->value; < if ($value !== null && isset($options[$record->name]['type'])) { < // this will convert string value to number if needed < $value = clean_param($value, $options[$record->name]['type']); < } < $this->formatoptions[$sectionid][$record->name] = $value; < } < } < }
> static $courseformatoptions = false; > if ($courseformatoptions === false) { > $courseformatoptions = array( > 'numsections' => array( > 'default' => 1, > 'type' => PARAM_INT, > ), > );
}
< return $this->formatoptions[$sectionid];
> return $courseformatoptions;
} /**
< * Adds format options elements to the course/section edit form < * < * This function is called from {@link course_edit_form::definition_after_data()}
> * Returns whether this course format allows the activity to > * have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
< * @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
> * @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 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;
> public function allow_stealth_module_visibility($cm, $section) { > return true;
} /**
< * Override if you need to perform some extra validation of the format options
> * Returns instance of page renderer used by the site page
*
< * @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
> * @param moodle_page $page the current page > * @return renderer_base
*/
< public function edit_form_validation($data, $files, $errors) { < return array();
> public function get_renderer(moodle_page $page) { > return new site_renderer($page, null);
} /**
< * Prepares values of course or section format options before storing them in DB
> * Site format uses only section 1.
*
< * 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
> * @return int
*/
< 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' => [[]]]; < $data[$key] = clean_param($value, $option['type']); < if ($option['element_type'] === 'select' && !array_key_exists($data[$key], $option['element_attributes'][0])) { < // Value invalid for select element, skip. < unset($data[$key]);
> public function get_section_number(): int { > return 1;
} }
< 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
> * 'Converts' a value from what is stored in the database into what is used by edit forms.
*
< * If $data does not contain property with the option name, the option will not be updated < * < * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data < * @param null|int 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
> * @param array $dest The destination array > * @param array $source The source array > * @param array $option The definition structure of the option. > * @param string $optionname The name of the option, as provided in the definition.
*/
< 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();
> function contract_value(array &$dest, array $source, array $option, string $optionname) : void { > if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor. > $name = substr($optionname, 0, -7); > if (isset($source[$name])) { > $dest[$optionname] = [ > 'text' => clean_param_if_not_null($source[$name], $option['type'] ?? PARAM_RAW), > 'format' => clean_param_if_not_null($source[$name . 'format'], PARAM_INT), > ];
}
< 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']; < } < $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) { < rebuild_course_cache($this->courseid, true); < } < 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 {@link moodleform::get_data()} or array with data < * @param stdClass $oldcourse if this function is called from {@link 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 {@link 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 {@link format_base::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'); < $context = context_course::instance($this->courseid); < if (!array_key_exists('course', $customdata)) { < $customdata['course'] = $this->get_course();
> if (isset($source[$optionname])) { > $dest[$optionname] = clean_param_if_not_null($source[$optionname], $option['type'] ?? PARAM_RAW);
}
< 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 {@link format_base::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 {@link format_base::course_header()} for usage
> * Cleans the given param, unless it is null.
*
< * @return null|renderable null for no output or object with data for plugin renderer
> * @param mixed $param The variable we are cleaning. > * @param string $type Expected format of param after cleaning. > * @return mixed Null if $param is null, otherwise the cleaned value. > * @throws coding_exception
*/
< public function course_content_header() {
> function clean_param_if_not_null($param, string $type = PARAM_RAW) { > if ($param === null) {
return null;
< } < < /** < * Course-specific information to be output immediately below content on any course page < * < * See {@link format_base::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) { < return $page->get_renderer('format_'. $this->get_format()); < } < < /** < * 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 clean_param($param, $type);
}
< return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);
} /**
< * 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
> * 'Converts' a value from what is used in edit forms into a value(s) to be stored in the database.
*
< * 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 {@link course_can_delete_section()} < * < * @param int|stdClass|section_info $section < * @return bool
> * @param array $dest The destination array > * @param array $source The source array > * @param array $option The definition structure of the option. > * @param string $optionname The name of the option, as provided in the definition.
*/
< public function can_delete_section($section) { < return false; < } < < /** < * Deletes a section < * < * Do not call this function directly, instead call {@link course_delete_section()} < * < * @param int|stdClass|section_info $section < * @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($section, $forcedeleteifnotempty = false) { < global $DB; < if (!$this->uses_sections()) { < // Not possible to delete section if sections are not used. < return false; < } < if (!is_object($section)) { < $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section), < '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)); < rebuild_course_cache($course->id, 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; < } < < /** < * 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';
> function expand_value(array &$dest, array $source, array $option, string $optionname) : void { > if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor. > $name = substr($optionname, 0, -7); > if (is_string($source[$optionname])) { > $dest[$name] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW); > $dest[$name . 'format'] = 1;
} 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 moodleform $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;
> $dest[$name] = clean_param($source[$optionname]['text'], $option['type'] ?? PARAM_RAW); > $dest[$name . 'format'] = clean_param($source[$optionname]['format'], PARAM_INT);
}
> unset($dest[$optionname]); } > } else { // Return false by default. > $dest[$optionname] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW);
< // Return false by default. < return false;
/**
< * Get the start date value from the course settings page form.
> * Course-module fragment renderer method.
*
< * @param moodleform $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.
> * The fragment arguments are id and sr (section return).
*
< * @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
> * @param array $args The fragment arguments. > * @return string The rendered cm item.
*
< * Course formats should register < * < * @param stdClass|section_info $section < * @param string $action < * @param int $sr < * @return null|array|stdClass any data for the Javascript post-processor (must be json-encodeable)
> * @throws require_login_exception
*/
< public function section_action($section, $action, $sr) {
> function core_courseformat_output_fragment_cmitem($args): string {
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); < 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; < default: < throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));
> [$course, $cm] = get_course_and_cm_from_cmid($args['id']); > if (!can_access_course($course, null, '', true) || !$cm->uservisible) { > throw new require_login_exception('Activity is not available');
}
< $modules = []; < < $modinfo = get_fast_modinfo($course); < $coursesections = $modinfo->sections; < if (array_key_exists($section->section, $coursesections)) { < $courserenderer = $PAGE->get_renderer('core', 'course'); < $completioninfo = new completion_info($course); < foreach ($coursesections[$section->section] as $cmid) { < $cm = $modinfo->get_cm($cmid); < $modules[] = $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sr); < }
> $format = course_get_format($course); > if (!empty($args['sr'])) { > $format->set_section_number($args['sr']);
}
< < return ['modules' => $modules];
> $renderer = $format->get_renderer($PAGE); > $section = $cm->get_section_info(); > return $renderer->course_section_updated_cm_item($format, $section, $cm);
} /**
< * 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.
> * Section fragment renderer method.
*
< * @return array the list of configs < * @since Moodle 3.5 < */ < public function get_config_for_external() { < return array(); < } < } < < /** < * Pseudo course format used for the site main page
> * The fragment arguments are courseid, section id and sr (section return).
*
< * @package core_course < * @copyright 2012 Marina Glancy < * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later < */ < class format_site extends format_base { < < /** < * Returns the display name of the given section that the course prefers.
> * @param array $args The fragment arguments. > * @return string The rendered section.
*
< * @param int|stdClass $section Section object from database or just field section.section < * @return Display name that the course format prefers, e.g. "Topic 2"
> * @throws require_login_exception
*/
< function get_section_name($section) { < return get_string('site'); < }
> function core_courseformat_output_fragment_section($args): string { > global $PAGE;
< /** < * For this fake course referring to the whole site, the site homepage is always returned < * regardless of arguments < * < * @param int|stdClass $section < * @param array $options < * @return null|moodle_url < */ < public function get_view_url($section, $options = array()) { < return new moodle_url('/', array('redirect' => 0));
> $course = get_course($args['courseid']); > if (!can_access_course($course, null, '', true)) { > throw new require_login_exception('Course is not available');
}
< /** < * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed < * < * @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() { < return blocks_get_default_site_course_blocks();
> $format = course_get_format($course); > if (!empty($args['sr'])) { > $format->set_section_number($args['sr']);
}
< /** < * Definitions of the additional options that site uses < * < * @param bool $foreditform < * @return array of options < */ < public function course_format_options($foreditform = false) { < static $courseformatoptions = false; < if ($courseformatoptions === false) { < $courseformatoptions = array( < 'numsections' => array( < 'default' => 1, < 'type' => PARAM_INT, < ), < ); < } < return $courseformatoptions;
> $modinfo = $format->get_modinfo(); > $section = $modinfo->get_section_info_by_id($args['id'], MUST_EXIST); > if (!$section->uservisible) { > throw new require_login_exception('Section is not available');
}
< /** < * 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 true; < }
> $renderer = $format->get_renderer($PAGE); > return $renderer->course_section_updated($format, $section);
}