MDL-34921 course/format: Fix unwanted redirects from Site home
[moodle.git] / course / format / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Base class for course format plugins
19  *
20  * @package    core_course
21  * @copyright  2012 Marina Glancy
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die;
27 /**
28  * Returns an instance of format class (extending format_base) for given course
29  *
30  * @param int|stdClass $courseorid either course id or
31  *     an object that has the property 'format' and may contain property 'id'
32  * @return format_base
33  */
34 function course_get_format($courseorid) {
35     return format_base::instance($courseorid);
36 }
38 /**
39  * Base class for course formats
40  *
41  * Each course format must declare class
42  * class format_FORMATNAME extends format_base {}
43  * in file lib.php
44  *
45  * For each course just one instance of this class is created and it will always be returned by
46  * course_get_format($courseorid). Format may store it's specific course-dependent options in
47  * variables of this class.
48  *
49  * In rare cases instance of child class may be created just for format without course id
50  * i.e. to check if format supports AJAX.
51  *
52  * Also course formats may extend class section_info and overwrite
53  * format_base::build_section_cache() to return more information about sections.
54  *
55  * If you are upgrading from Moodle 2.3 start with copying the class format_legacy and renaming
56  * it to format_FORMATNAME, then move the code from your callback functions into
57  * appropriate functions of the class.
58  *
59  * @package    core_course
60  * @copyright  2012 Marina Glancy
61  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
62  */
63 abstract class format_base {
64     /** @var int Id of the course in this instance (maybe 0) */
65     protected $courseid;
66     /** @var string format used for this course. Please note that it can be different from
67      * course.format field if course referes to non-existing of disabled format */
68     protected $format;
69     /** @var stdClass data for course object, please use {@link format_base::get_course()} */
70     protected $course = false;
71     /** @var array caches format options, please use {@link format_base::get_format_options()} */
72     protected $formatoptions = array();
73     /** @var array cached instances */
74     private static $instances = array();
75     /** @var array plugin name => class name. */
76     private static $classesforformat = array('site' => 'site');
78     /**
79      * Creates a new instance of class
80      *
81      * Please use {@link course_get_format($courseorid)} to get an instance of the format class
82      *
83      * @param string $format
84      * @param int $courseid
85      * @return format_base
86      */
87     protected function __construct($format, $courseid) {
88         $this->format = $format;
89         $this->courseid = $courseid;
90     }
92     /**
93      * Validates that course format exists and enabled and returns either itself or default format
94      *
95      * @param string $format
96      * @return string
97      */
98     protected static final function get_format_or_default($format) {
99         if (array_key_exists($format, self::$classesforformat)) {
100             return self::$classesforformat[$format];
101         }
103         $plugins = get_sorted_course_formats();
104         foreach ($plugins as $plugin) {
105             self::$classesforformat[$plugin] = $plugin;
106         }
108         if (array_key_exists($format, self::$classesforformat)) {
109             return self::$classesforformat[$format];
110         }
112         if (PHPUNIT_TEST && class_exists('format_' . $format)) {
113             // Allow unittests to use non-existing course formats.
114             return $format;
115         }
117         // Else return default format
118         $defaultformat = get_config('moodlecourse', 'format');
119         if (!in_array($defaultformat, $plugins)) {
120             // when default format is not set correctly, use the first available format
121             $defaultformat = reset($plugins);
122         }
123         debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
125         self::$classesforformat[$format] = $defaultformat;
126         return $defaultformat;
127     }
129     /**
130      * Get class name for the format
131      *
132      * If course format xxx does not declare class format_xxx, format_legacy will be returned.
133      * This function also includes lib.php file from corresponding format plugin
134      *
135      * @param string $format
136      * @return string
137      */
138     protected static final function get_class_name($format) {
139         global $CFG;
140         static $classnames = array('site' => 'format_site');
141         if (!isset($classnames[$format])) {
142             $plugins = core_component::get_plugin_list('format');
143             $usedformat = self::get_format_or_default($format);
144             if (isset($plugins[$usedformat]) && file_exists($plugins[$usedformat].'/lib.php')) {
145                 require_once($plugins[$usedformat].'/lib.php');
146             }
147             $classnames[$format] = 'format_'. $usedformat;
148             if (!class_exists($classnames[$format])) {
149                 require_once($CFG->dirroot.'/course/format/formatlegacy.php');
150                 $classnames[$format] = 'format_legacy';
151             }
152         }
153         return $classnames[$format];
154     }
156     /**
157      * Returns an instance of the class
158      *
159      * @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances
160      *
161      * @param int|stdClass $courseorid either course id or
162      *     an object that has the property 'format' and may contain property 'id'
163      * @return format_base
164      */
165     public static final function instance($courseorid) {
166         global $DB;
167         if (!is_object($courseorid)) {
168             $courseid = (int)$courseorid;
169             if ($courseid && isset(self::$instances[$courseid]) && count(self::$instances[$courseid]) == 1) {
170                 $formats = array_keys(self::$instances[$courseid]);
171                 $format = reset($formats);
172             } else {
173                 $format = $DB->get_field('course', 'format', array('id' => $courseid), MUST_EXIST);
174             }
175         } else {
176             $format = $courseorid->format;
177             if (isset($courseorid->id)) {
178                 $courseid = clean_param($courseorid->id, PARAM_INT);
179             } else {
180                 $courseid = 0;
181             }
182         }
183         // validate that format exists and enabled, use default otherwise
184         $format = self::get_format_or_default($format);
185         if (!isset(self::$instances[$courseid][$format])) {
186             $classname = self::get_class_name($format);
187             self::$instances[$courseid][$format] = new $classname($format, $courseid);
188         }
189         return self::$instances[$courseid][$format];
190     }
192     /**
193      * Resets cache for the course (or all caches)
194      * To be called from {@link rebuild_course_cache()}
195      *
196      * @param int $courseid
197      */
198     public static final function reset_course_cache($courseid = 0) {
199         if ($courseid) {
200             if (isset(self::$instances[$courseid])) {
201                 foreach (self::$instances[$courseid] as $format => $object) {
202                     // in case somebody keeps the reference to course format object
203                     self::$instances[$courseid][$format]->course = false;
204                     self::$instances[$courseid][$format]->formatoptions = array();
205                 }
206                 unset(self::$instances[$courseid]);
207             }
208         } else {
209             self::$instances = array();
210         }
211     }
213     /**
214      * Returns the format name used by this course
215      *
216      * @return string
217      */
218     public final function get_format() {
219         return $this->format;
220     }
222     /**
223      * Returns id of the course (0 if course is not specified)
224      *
225      * @return int
226      */
227     public final function get_courseid() {
228         return $this->courseid;
229     }
231     /**
232      * Returns a record from course database table plus additional fields
233      * that course format defines
234      *
235      * @return stdClass
236      */
237     public function get_course() {
238         global $DB;
239         if (!$this->courseid) {
240             return null;
241         }
242         if ($this->course === false) {
243             $this->course = get_course($this->courseid);
244             $options = $this->get_format_options();
245             $dbcoursecolumns = null;
246             foreach ($options as $optionname => $optionvalue) {
247                 if (isset($this->course->$optionname)) {
248                     // Course format options must not have the same names as existing columns in db table "course".
249                     if (!isset($dbcoursecolumns)) {
250                         $dbcoursecolumns = $DB->get_columns('course');
251                     }
252                     if (isset($dbcoursecolumns[$optionname])) {
253                         debugging('The option name '.$optionname.' in course format '.$this->format.
254                             ' is invalid because the field with the same name exists in {course} table',
255                             DEBUG_DEVELOPER);
256                         continue;
257                     }
258                 }
259                 $this->course->$optionname = $optionvalue;
260             }
261         }
262         return $this->course;
263     }
265     /**
266      * Returns true if the course has a front page.
267      *
268      * This function is called to determine if the course has a view page, whether or not
269      * it contains a listing of activities. It can be useful to set this to false when the course
270      * format has only one activity and ignores the course page. Or if there are multiple
271      * activities but no page to see the centralised information.
272      *
273      * Initially this was created to know if forms should add a button to return to the course page.
274      * So if 'Return to course' does not make sense in your format your should probably return false.
275      *
276      * @return boolean
277      * @since Moodle 2.6
278      */
279     public function has_view_page() {
280         return true;
281     }
283     /**
284      * Returns true if this course format uses sections
285      *
286      * This function may be called without specifying the course id
287      * i.e. in {@link course_format_uses_sections()}
288      *
289      * Developers, note that if course format does use sections there should be defined a language
290      * string with the name 'sectionname' defining what the section relates to in the format, i.e.
291      * $string['sectionname'] = 'Topic';
292      * or
293      * $string['sectionname'] = 'Week';
294      *
295      * @return bool
296      */
297     public function uses_sections() {
298         return false;
299     }
301     /**
302      * Returns a list of sections used in the course
303      *
304      * This is a shortcut to get_fast_modinfo()->get_section_info_all()
305      * @see get_fast_modinfo()
306      * @see course_modinfo::get_section_info_all()
307      *
308      * @return array of section_info objects
309      */
310     public final function get_sections() {
311         if ($course = $this->get_course()) {
312             $modinfo = get_fast_modinfo($course);
313             return $modinfo->get_section_info_all();
314         }
315         return array();
316     }
318     /**
319      * Returns information about section used in course
320      *
321      * @param int|stdClass $section either section number (field course_section.section) or row from course_section table
322      * @param int $strictness
323      * @return section_info
324      */
325     public final function get_section($section, $strictness = IGNORE_MISSING) {
326         if (is_object($section)) {
327             $sectionnum = $section->section;
328         } else {
329             $sectionnum = $section;
330         }
331         $sections = $this->get_sections();
332         if (array_key_exists($sectionnum, $sections)) {
333             return $sections[$sectionnum];
334         }
335         if ($strictness == MUST_EXIST) {
336             throw new moodle_exception('sectionnotexist');
337         }
338         return null;
339     }
341     /**
342      * Returns the display name of the given section that the course prefers.
343      *
344      * @param int|stdClass $section Section object from database or just field course_sections.section
345      * @return Display name that the course format prefers, e.g. "Topic 2"
346      */
347     public function get_section_name($section) {
348         if (is_object($section)) {
349             $sectionnum = $section->section;
350         } else {
351             $sectionnum = $section;
352         }
353         return get_string('sectionname', 'format_'.$this->format) . ' ' . $sectionnum;
354     }
356     /**
357      * Returns the information about the ajax support in the given source format
358      *
359      * The returned object's property (boolean)capable indicates that
360      * the course format supports Moodle course ajax features.
361      *
362      * @return stdClass
363      */
364     public function supports_ajax() {
365         // no support by default
366         $ajaxsupport = new stdClass();
367         $ajaxsupport->capable = false;
368         return $ajaxsupport;
369     }
371     /**
372      * Custom action after section has been moved in AJAX mode
373      *
374      * Used in course/rest.php
375      *
376      * @return array This will be passed in ajax respose
377      */
378     public function ajax_section_move() {
379         return null;
380     }
382     /**
383      * The URL to use for the specified course (with section)
384      *
385      * Please note that course view page /course/view.php?id=COURSEID is hardcoded in many
386      * places in core and contributed modules. If course format wants to change the location
387      * of the view script, it is not enough to change just this function. Do not forget
388      * to add proper redirection.
389      *
390      * @param int|stdClass $section Section object from database or just field course_sections.section
391      *     if null the course view page is returned
392      * @param array $options options for view URL. At the moment core uses:
393      *     'navigation' (bool) if true and section has no separate page, the function returns null
394      *     'sr' (int) used by multipage formats to specify to which section to return
395      * @return null|moodle_url
396      */
397     public function get_view_url($section, $options = array()) {
398         global $CFG;
399         $course = $this->get_course();
400         $url = new moodle_url('/course/view.php', array('id' => $course->id));
402         if (array_key_exists('sr', $options)) {
403             $sectionno = $options['sr'];
404         } else if (is_object($section)) {
405             $sectionno = $section->section;
406         } else {
407             $sectionno = $section;
408         }
409         if (empty($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) {
410             // by default assume that sections are never displayed on separate pages
411             return null;
412         }
413         if ($this->uses_sections() && $sectionno !== null) {
414             $url->set_anchor('section-'.$sectionno);
415         }
416         return $url;
417     }
419     /**
420      * Loads all of the course sections into the navigation
421      *
422      * This method is called from {@link global_navigation::load_course_sections()}
423      *
424      * By default the method {@link global_navigation::load_generic_course_sections()} is called
425      *
426      * When overwriting please note that navigationlib relies on using the correct values for
427      * arguments $type and $key in {@link navigation_node::add()}
428      *
429      * Example of code creating a section node:
430      * $sectionnode = $node->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);
431      * $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
432      *
433      * Example of code creating an activity node:
434      * $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
435      * if (global_navigation::module_extends_navigation($activity->modname)) {
436      *     $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
437      * } else {
438      *     $activitynode->nodetype = navigation_node::NODETYPE_LEAF;
439      * }
440      *
441      * Also note that if $navigation->includesectionnum is not null, the section with this relative
442      * number needs is expected to be loaded
443      *
444      * @param global_navigation $navigation
445      * @param navigation_node $node The course node within the navigation
446      */
447     public function extend_course_navigation($navigation, navigation_node $node) {
448         if ($course = $this->get_course()) {
449             $navigation->load_generic_course_sections($course, $node);
450         }
451         return array();
452     }
454     /**
455      * Returns the list of blocks to be automatically added for the newly created course
456      *
457      * @see blocks_add_default_course_blocks()
458      *
459      * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
460      *     each of values is an array of block names (for left and right side columns)
461      */
462     public function get_default_blocks() {
463         global $CFG;
464         if (!empty($CFG->defaultblocks)){
465             return blocks_parse_default_blocks_list($CFG->defaultblocks);
466         }
467         $blocknames = array(
468             BLOCK_POS_LEFT => array(),
469             BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
470         );
471         return $blocknames;
472     }
474     /**
475      * Returns the localised name of this course format plugin
476      *
477      * @return lang_string
478      */
479     public final function get_format_name() {
480         return new lang_string('pluginname', 'format_'.$this->get_format());
481     }
483     /**
484      * Definitions of the additional options that this course format uses for course
485      *
486      * This function may be called often, it should be as fast as possible.
487      * Avoid using get_string() method, use "new lang_string()" instead
488      * It is not recommended to use dynamic or course-dependant expressions here
489      * This function may be also called when course does not exist yet.
490      *
491      * Option names must be different from fields in the {course} talbe or any form elements on
492      * course edit form, it may even make sence to use special prefix for them.
493      *
494      * Each option must have the option name as a key and the array of properties as a value:
495      * 'default' - default value for this option (assumed null if not specified)
496      * 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.)
497      *
498      * Additional properties used by default implementation of
499      * {@link format_base::create_edit_form_elements()} (calls this method with $foreditform = true)
500      * 'label' - localised human-readable label for the edit form
501      * 'element_type' - type of the form element, default 'text'
502      * 'element_attributes' - additional attributes for the form element, these are 4th and further
503      *    arguments in the moodleform::addElement() method
504      * 'help' - string for help button. Note that if 'help' value is 'myoption' then the string with
505      *    the name 'myoption_help' must exist in the language file
506      * 'help_component' - language component to look for help string, by default this the component
507      *    for this course format
508      *
509      * This is an interface for creating simple form elements. If format plugin wants to use other
510      * methods such as disableIf, it can be done by overriding create_edit_form_elements().
511      *
512      * Course format options can be accessed as:
513      * $this->get_course()->OPTIONNAME (inside the format class)
514      * course_get_format($course)->get_course()->OPTIONNAME (outside of format class)
515      *
516      * All course options are returned by calling:
517      * $this->get_format_options();
518      *
519      * @param bool $foreditform
520      * @return array of options
521      */
522     public function course_format_options($foreditform = false) {
523         return array();
524     }
526     /**
527      * Definitions of the additional options that this course format uses for section
528      *
529      * See {@link format_base::course_format_options()} for return array definition.
530      *
531      * Additionally section format options may have property 'cache' set to true
532      * if this option needs to be cached in {@link get_fast_modinfo()}. The 'cache' property
533      * is recommended to be set only for fields used in {@link format_base::get_section_name()},
534      * {@link format_base::extend_course_navigation()} and {@link format_base::get_view_url()}
535      *
536      * For better performance cached options are recommended to have 'cachedefault' property
537      * Unlike 'default', 'cachedefault' should be static and not access get_config().
538      *
539      * Regardless of value of 'cache' all options are accessed in the code as
540      * $sectioninfo->OPTIONNAME
541      * where $sectioninfo is instance of section_info, returned by
542      * get_fast_modinfo($course)->get_section_info($sectionnum)
543      * or get_fast_modinfo($course)->get_section_info_all()
544      *
545      * All format options for particular section are returned by calling:
546      * $this->get_format_options($section);
547      *
548      * @param bool $foreditform
549      * @return array
550      */
551     public function section_format_options($foreditform = false) {
552         return array();
553     }
555     /**
556      * Returns the format options stored for this course or course section
557      *
558      * When overriding please note that this function is called from rebuild_course_cache()
559      * and section_info object, therefore using of get_fast_modinfo() and/or any function that
560      * accesses it may lead to recursion.
561      *
562      * @param null|int|stdClass|section_info $section if null the course format options will be returned
563      *     otherwise options for specified section will be returned. This can be either
564      *     section object or relative section number (field course_sections.section)
565      * @return array
566      */
567     public function get_format_options($section = null) {
568         global $DB;
569         if ($section === null) {
570             $options = $this->course_format_options();
571         } else {
572             $options = $this->section_format_options();
573         }
574         if (empty($options)) {
575             // there are no option for course/sections anyway, no need to go further
576             return array();
577         }
578         if ($section === null) {
579             // course format options will be returned
580             $sectionid = 0;
581         } else if ($this->courseid && isset($section->id)) {
582             // course section format options will be returned
583             $sectionid = $section->id;
584         } else if ($this->courseid && is_int($section) &&
585                 ($sectionobj = $DB->get_record('course_sections',
586                         array('section' => $section, 'course' => $this->courseid), 'id'))) {
587             // course section format options will be returned
588             $sectionid = $sectionobj->id;
589         } else {
590             // non-existing (yet) section was passed as an argument
591             // default format options for course section will be returned
592             $sectionid = -1;
593         }
594         if (!array_key_exists($sectionid, $this->formatoptions)) {
595             $this->formatoptions[$sectionid] = array();
596             // first fill with default values
597             foreach ($options as $optionname => $optionparams) {
598                 $this->formatoptions[$sectionid][$optionname] = null;
599                 if (array_key_exists('default', $optionparams)) {
600                     $this->formatoptions[$sectionid][$optionname] = $optionparams['default'];
601                 }
602             }
603             if ($this->courseid && $sectionid !== -1) {
604                 // overwrite the default options values with those stored in course_format_options table
605                 // nothing can be stored if we are interested in generic course ($this->courseid == 0)
606                 // or generic section ($sectionid === 0)
607                 $records = $DB->get_records('course_format_options',
608                         array('courseid' => $this->courseid,
609                               'format' => $this->format,
610                               'sectionid' => $sectionid
611                             ), '', 'id,name,value');
612                 foreach ($records as $record) {
613                     if (array_key_exists($record->name, $this->formatoptions[$sectionid])) {
614                         $value = $record->value;
615                         if ($value !== null && isset($options[$record->name]['type'])) {
616                             // this will convert string value to number if needed
617                             $value = clean_param($value, $options[$record->name]['type']);
618                         }
619                         $this->formatoptions[$sectionid][$record->name] = $value;
620                     }
621                 }
622             }
623         }
624         return $this->formatoptions[$sectionid];
625     }
627     /**
628      * Adds format options elements to the course/section edit form
629      *
630      * This function is called from {@link course_edit_form::definition_after_data()}
631      *
632      * @param MoodleQuickForm $mform form the elements are added to
633      * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form
634      * @return array array of references to the added form elements
635      */
636     public function create_edit_form_elements(&$mform, $forsection = false) {
637         $elements = array();
638         if ($forsection) {
639             $options = $this->section_format_options(true);
640         } else {
641             $options = $this->course_format_options(true);
642         }
643         foreach ($options as $optionname => $option) {
644             if (!isset($option['element_type'])) {
645                 $option['element_type'] = 'text';
646             }
647             $args = array($option['element_type'], $optionname, $option['label']);
648             if (!empty($option['element_attributes'])) {
649                 $args = array_merge($args, $option['element_attributes']);
650             }
651             $elements[] = call_user_func_array(array($mform, 'addElement'), $args);
652             if (isset($option['help'])) {
653                 $helpcomponent = 'format_'. $this->get_format();
654                 if (isset($option['help_component'])) {
655                     $helpcomponent = $option['help_component'];
656                 }
657                 $mform->addHelpButton($optionname, $option['help'], $helpcomponent);
658             }
659             if (isset($option['type'])) {
660                 $mform->setType($optionname, $option['type']);
661             }
662             if (is_null($mform->getElementValue($optionname)) && isset($option['default'])) {
663                 $mform->setDefault($optionname, $option['default']);
664             }
665         }
666         return $elements;
667     }
669     /**
670      * Override if you need to perform some extra validation of the format options
671      *
672      * @param array $data array of ("fieldname"=>value) of submitted data
673      * @param array $files array of uploaded files "element_name"=>tmp_file_path
674      * @param array $errors errors already discovered in edit form validation
675      * @return array of "element_name"=>"error_description" if there are errors,
676      *         or an empty array if everything is OK.
677      *         Do not repeat errors from $errors param here
678      */
679     public function edit_form_validation($data, $files, $errors) {
680         return array();
681     }
683     /**
684      * Updates format options for a course or section
685      *
686      * If $data does not contain property with the option name, the option will not be updated
687      *
688      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
689      * @param null|int null if these are options for course or section id (course_sections.id)
690      *     if these are options for section
691      * @return bool whether there were any changes to the options values
692      */
693     protected function update_format_options($data, $sectionid = null) {
694         global $DB;
695         if (!$sectionid) {
696             $allformatoptions = $this->course_format_options();
697             $sectionid = 0;
698         } else {
699             $allformatoptions = $this->section_format_options();
700         }
701         if (empty($allformatoptions)) {
702             // nothing to update anyway
703             return false;
704         }
705         $defaultoptions = array();
706         $cached = array();
707         foreach ($allformatoptions as $key => $option) {
708             $defaultoptions[$key] = null;
709             if (array_key_exists('default', $option)) {
710                 $defaultoptions[$key] = $option['default'];
711             }
712             $cached[$key] = ($sectionid === 0 || !empty($option['cache']));
713         }
714         $records = $DB->get_records('course_format_options',
715                 array('courseid' => $this->courseid,
716                       'format' => $this->format,
717                       'sectionid' => $sectionid
718                     ), '', 'name,id,value');
719         $changed = $needrebuild = false;
720         $data = (array)$data;
721         foreach ($defaultoptions as $key => $value) {
722             if (isset($records[$key])) {
723                 if (array_key_exists($key, $data) && $records[$key]->value !== $data[$key]) {
724                     $DB->set_field('course_format_options', 'value',
725                             $data[$key], array('id' => $records[$key]->id));
726                     $changed = true;
727                     $needrebuild = $needrebuild || $cached[$key];
728                 }
729             } else {
730                 if (array_key_exists($key, $data) && $data[$key] !== $value) {
731                     $newvalue = $data[$key];
732                     $changed = true;
733                     $needrebuild = $needrebuild || $cached[$key];
734                 } else {
735                     $newvalue = $value;
736                     // we still insert entry in DB but there are no changes from user point of
737                     // view and no need to call rebuild_course_cache()
738                 }
739                 $DB->insert_record('course_format_options', array(
740                     'courseid' => $this->courseid,
741                     'format' => $this->format,
742                     'sectionid' => $sectionid,
743                     'name' => $key,
744                     'value' => $newvalue
745                 ));
746             }
747         }
748         if ($needrebuild) {
749             rebuild_course_cache($this->courseid, true);
750         }
751         if ($changed) {
752             // reset internal caches
753             if (!$sectionid) {
754                 $this->course = false;
755             }
756             unset($this->formatoptions[$sectionid]);
757         }
758         return $changed;
759     }
761     /**
762      * Updates format options for a course
763      *
764      * If $data does not contain property with the option name, the option will not be updated
765      *
766      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
767      * @param stdClass $oldcourse if this function is called from {@link update_course()}
768      *     this object contains information about the course before update
769      * @return bool whether there were any changes to the options values
770      */
771     public function update_course_format_options($data, $oldcourse = null) {
772         return $this->update_format_options($data);
773     }
775     /**
776      * Updates format options for a section
777      *
778      * Section id is expected in $data->id (or $data['id'])
779      * If $data does not contain property with the option name, the option will not be updated
780      *
781      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
782      * @return bool whether there were any changes to the options values
783      */
784     public function update_section_format_options($data) {
785         $data = (array)$data;
786         return $this->update_format_options($data, $data['id']);
787     }
789     /**
790      * Return an instance of moodleform to edit a specified section
791      *
792      * Default implementation returns instance of editsection_form that automatically adds
793      * additional fields defined in {@link format_base::section_format_options()}
794      *
795      * Format plugins may extend editsection_form if they want to have custom edit section form.
796      *
797      * @param mixed $action the action attribute for the form. If empty defaults to auto detect the
798      *              current url. If a moodle_url object then outputs params as hidden variables.
799      * @param array $customdata the array with custom data to be passed to the form
800      *     /course/editsection.php passes section_info object in 'cs' field
801      *     for filling availability fields
802      * @return moodleform
803      */
804     public function editsection_form($action, $customdata = array()) {
805         global $CFG;
806         require_once($CFG->dirroot. '/course/editsection_form.php');
807         $context = context_course::instance($this->courseid);
808         if (!array_key_exists('course', $customdata)) {
809             $customdata['course'] = $this->get_course();
810         }
811         return new editsection_form($action, $customdata);
812     }
814     /**
815      * Allows course format to execute code on moodle_page::set_course()
816      *
817      * @param moodle_page $page instance of page calling set_course
818      */
819     public function page_set_course(moodle_page $page) {
820     }
822     /**
823      * Allows course format to execute code on moodle_page::set_cm()
824      *
825      * Current module can be accessed as $page->cm (returns instance of cm_info)
826      *
827      * @param moodle_page $page instance of page calling set_cm
828      */
829     public function page_set_cm(moodle_page $page) {
830     }
832     /**
833      * Course-specific information to be output on any course page (usually above navigation bar)
834      *
835      * Example of usage:
836      * define
837      * class format_FORMATNAME_XXX implements renderable {}
838      *
839      * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:
840      * class format_FORMATNAME_renderer extends plugin_renderer_base {
841      *     protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {
842      *         return html_writer::tag('div', 'This is my header/footer');
843      *     }
844      * }
845      *
846      * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from
847      * plugin renderer will be called
848      *
849      * @return null|renderable null for no output or object with data for plugin renderer
850      */
851     public function course_header() {
852         return null;
853     }
855     /**
856      * Course-specific information to be output on any course page (usually in the beginning of
857      * standard footer)
858      *
859      * See {@link format_base::course_header()} for usage
860      *
861      * @return null|renderable null for no output or object with data for plugin renderer
862      */
863     public function course_footer() {
864         return null;
865     }
867     /**
868      * Course-specific information to be output immediately above content on any course page
869      *
870      * See {@link format_base::course_header()} for usage
871      *
872      * @return null|renderable null for no output or object with data for plugin renderer
873      */
874     public function course_content_header() {
875         return null;
876     }
878     /**
879      * Course-specific information to be output immediately below content on any course page
880      *
881      * See {@link format_base::course_header()} for usage
882      *
883      * @return null|renderable null for no output or object with data for plugin renderer
884      */
885     public function course_content_footer() {
886         return null;
887     }
889     /**
890      * Returns instance of page renderer used by this plugin
891      *
892      * @param moodle_page $page
893      * @return renderer_base
894      */
895     public function get_renderer(moodle_page $page) {
896         return $page->get_renderer('format_'. $this->get_format());
897     }
899     /**
900      * Returns true if the specified section is current
901      *
902      * By default we analyze $course->marker
903      *
904      * @param int|stdClass|section_info $section
905      * @return bool
906      */
907     public function is_section_current($section) {
908         if (is_object($section)) {
909             $sectionnum = $section->section;
910         } else {
911             $sectionnum = $section;
912         }
913         return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);
914     }
916     /**
917      * Allows to specify for modinfo that section is not available even when it is visible and conditionally available.
918      *
919      * Note: affected user can be retrieved as: $section->modinfo->userid
920      *
921      * Course format plugins can override the method to change the properties $available and $availableinfo that were
922      * calculated by conditional availability.
923      * To make section unavailable set:
924      *     $available = false;
925      * To make unavailable section completely hidden set:
926      *     $availableinfo = '';
927      * To make unavailable section visible with availability message set:
928      *     $availableinfo = get_string('sectionhidden', 'format_xxx');
929      *
930      * @param section_info $section
931      * @param bool $available the 'available' propery of the section_info as it was evaluated by conditional availability.
932      *     Can be changed by the method but 'false' can not be overridden by 'true'.
933      * @param string $availableinfo the 'availableinfo' propery of the section_info as it was evaluated by conditional availability.
934      *     Can be changed by the method
935      */
936     public function section_get_available_hook(section_info $section, &$available, &$availableinfo) {
937     }
939     /**
940      * Whether this format allows to delete sections
941      *
942      * If format supports deleting sections it is also recommended to define language string
943      * 'deletesection' inside the format.
944      *
945      * Do not call this function directly, instead use {@link course_can_delete_section()}
946      *
947      * @param int|stdClass|section_info $section
948      * @return bool
949      */
950     public function can_delete_section($section) {
951         return false;
952     }
954     /**
955      * Deletes a section
956      *
957      * Do not call this function directly, instead call {@link course_delete_section()}
958      *
959      * @param int|stdClass|section_info $section
960      * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.
961      * @return bool whether section was deleted
962      */
963     public function delete_section($section, $forcedeleteifnotempty = false) {
964         global $DB;
965         if (!$this->uses_sections()) {
966             // Not possible to delete section if sections are not used.
967             return false;
968         }
969         if (!is_object($section)) {
970             $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section),
971                 'id,section,sequence');
972         }
973         if (!$section || !$section->section) {
974             // Not possible to delete 0-section.
975             return false;
976         }
978         if (!$forcedeleteifnotempty && !empty($section->sequence)) {
979             return false;
980         }
982         $course = $this->get_course();
984         // Remove the marker if it points to this section.
985         if ($section->section == $course->marker) {
986             course_set_marker($course->id, 0);
987         }
989         $lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
990                             WHERE course = ?', array($course->id));
992         // Find out if we need to descrease the 'numsections' property later.
993         $courseformathasnumsections = array_key_exists('numsections',
994             $this->get_format_options());
995         $decreasenumsections = $courseformathasnumsections && ($section->section <= $course->numsections);
997         // Move the section to the end.
998         move_section_to($course, $section->section, $lastsection, true);
1000         // Delete all modules from the section.
1001         foreach (preg_split('/,/', $section->sequence, -1, PREG_SPLIT_NO_EMPTY) as $cmid) {
1002             course_delete_module($cmid);
1003         }
1005         // Delete section and it's format options.
1006         $DB->delete_records('course_format_options', array('sectionid' => $section->id));
1007         $DB->delete_records('course_sections', array('id' => $section->id));
1008         rebuild_course_cache($course->id, true);
1010         // Descrease 'numsections' if needed.
1011         if ($decreasenumsections) {
1012             $this->update_course_format_options(array('numsections' => $course->numsections - 1));
1013         }
1015         return true;
1016     }
1019 /**
1020  * Pseudo course format used for the site main page
1021  *
1022  * @package    core_course
1023  * @copyright  2012 Marina Glancy
1024  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1025  */
1026 class format_site extends format_base {
1028     /**
1029      * Returns the display name of the given section that the course prefers.
1030      *
1031      * @param int|stdClass $section Section object from database or just field section.section
1032      * @return Display name that the course format prefers, e.g. "Topic 2"
1033      */
1034     function get_section_name($section) {
1035         return get_string('site');
1036     }
1038     /**
1039      * For this fake course referring to the whole site, the site homepage is always returned
1040      * regardless of arguments
1041      *
1042      * @param int|stdClass $section
1043      * @param array $options
1044      * @return null|moodle_url
1045      */
1046     public function get_view_url($section, $options = array()) {
1047         return new moodle_url('/', array('redirect' => 0));
1048     }
1050     /**
1051      * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed
1052      *
1053      * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
1054      *     each of values is an array of block names (for left and right side columns)
1055      */
1056     public function get_default_blocks() {
1057         return blocks_get_default_site_course_blocks();
1058     }
1060     /**
1061      * Definitions of the additional options that site uses
1062      *
1063      * @param bool $foreditform
1064      * @return array of options
1065      */
1066     public function course_format_options($foreditform = false) {
1067         static $courseformatoptions = false;
1068         if ($courseformatoptions === false) {
1069             $courseformatoptions = array(
1070                 'numsections' => array(
1071                     'default' => 1,
1072                     'type' => PARAM_INT,
1073                 ),
1074             );
1075         }
1076         return $courseformatoptions;
1077     }