Merge branch 'MDL-33099-master' of git://github.com/andrewnicols/moodle
[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         // Else return default format
113         $defaultformat = get_config('moodlecourse', 'format');
114         if (!in_array($defaultformat, $plugins)) {
115             // when default format is not set correctly, use the first available format
116             $defaultformat = reset($plugins);
117         }
118         debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
120         self::$classesforformat[$format] = $defaultformat;
121         return $defaultformat;
122     }
124     /**
125      * Get class name for the format
126      *
127      * If course format xxx does not declare class format_xxx, format_legacy will be returned.
128      * This function also includes lib.php file from corresponding format plugin
129      *
130      * @param string $format
131      * @return string
132      */
133     protected static final function get_class_name($format) {
134         global $CFG;
135         static $classnames = array('site' => 'format_site');
136         if (!isset($classnames[$format])) {
137             $plugins = core_component::get_plugin_list('format');
138             $usedformat = self::get_format_or_default($format);
139             if (file_exists($plugins[$usedformat].'/lib.php')) {
140                 require_once($plugins[$usedformat].'/lib.php');
141             }
142             $classnames[$format] = 'format_'. $usedformat;
143             if (!class_exists($classnames[$format])) {
144                 require_once($CFG->dirroot.'/course/format/formatlegacy.php');
145                 $classnames[$format] = 'format_legacy';
146             }
147         }
148         return $classnames[$format];
149     }
151     /**
152      * Returns an instance of the class
153      *
154      * @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances
155      *
156      * @param int|stdClass $courseorid either course id or
157      *     an object that has the property 'format' and may contain property 'id'
158      * @return format_base
159      */
160     public static final function instance($courseorid) {
161         global $DB;
162         if (!is_object($courseorid)) {
163             $courseid = (int)$courseorid;
164             if ($courseid && isset(self::$instances[$courseid]) && count(self::$instances[$courseid]) == 1) {
165                 $formats = array_keys(self::$instances[$courseid]);
166                 $format = reset($formats);
167             } else {
168                 $format = $DB->get_field('course', 'format', array('id' => $courseid), MUST_EXIST);
169             }
170         } else {
171             $format = $courseorid->format;
172             if (isset($courseorid->id)) {
173                 $courseid = clean_param($courseorid->id, PARAM_INT);
174             } else {
175                 $courseid = 0;
176             }
177         }
178         // validate that format exists and enabled, use default otherwise
179         $format = self::get_format_or_default($format);
180         if (!isset(self::$instances[$courseid][$format])) {
181             $classname = self::get_class_name($format);
182             self::$instances[$courseid][$format] = new $classname($format, $courseid);
183         }
184         return self::$instances[$courseid][$format];
185     }
187     /**
188      * Resets cache for the course (or all caches)
189      * To be called from {@link rebuild_course_cache()}
190      *
191      * @param int $courseid
192      */
193     public static final function reset_course_cache($courseid = 0) {
194         if ($courseid) {
195             if (isset(self::$instances[$courseid])) {
196                 foreach (self::$instances[$courseid] as $format => $object) {
197                     // in case somebody keeps the reference to course format object
198                     self::$instances[$courseid][$format]->course = false;
199                     self::$instances[$courseid][$format]->formatoptions = array();
200                 }
201                 unset(self::$instances[$courseid]);
202             }
203         } else {
204             self::$instances = array();
205         }
206     }
208     /**
209      * Returns the format name used by this course
210      *
211      * @return string
212      */
213     public final function get_format() {
214         return $this->format;
215     }
217     /**
218      * Returns id of the course (0 if course is not specified)
219      *
220      * @return int
221      */
222     public final function get_courseid() {
223         return $this->courseid;
224     }
226     /**
227      * Returns a record from course database table plus additional fields
228      * that course format defines
229      *
230      * @return stdClass
231      */
232     public function get_course() {
233         global $DB;
234         if (!$this->courseid) {
235             return null;
236         }
237         if ($this->course === false) {
238             $this->course = get_course($this->courseid);
239             $options = $this->get_format_options();
240             $dbcoursecolumns = null;
241             foreach ($options as $optionname => $optionvalue) {
242                 if (isset($this->course->$optionname)) {
243                     // Course format options must not have the same names as existing columns in db table "course".
244                     if (!isset($dbcoursecolumns)) {
245                         $dbcoursecolumns = $DB->get_columns('course');
246                     }
247                     if (isset($dbcoursecolumns[$optionname])) {
248                         debugging('The option name '.$optionname.' in course format '.$this->format.
249                             ' is invalid because the field with the same name exists in {course} table',
250                             DEBUG_DEVELOPER);
251                         continue;
252                     }
253                 }
254                 $this->course->$optionname = $optionvalue;
255             }
256         }
257         return $this->course;
258     }
260     /**
261      * Returns true if the course has a front page.
262      *
263      * This function is called to determine if the course has a view page, whether or not
264      * it contains a listing of activities. It can be useful to set this to false when the course
265      * format has only one activity and ignores the course page. Or if there are multiple
266      * activities but no page to see the centralised information.
267      *
268      * Initially this was created to know if forms should add a button to return to the course page.
269      * So if 'Return to course' does not make sense in your format your should probably return false.
270      *
271      * @return boolean
272      * @since 2.6
273      */
274     public function has_view_page() {
275         return true;
276     }
278     /**
279      * Returns true if this course format uses sections
280      *
281      * This function may be called without specifying the course id
282      * i.e. in {@link course_format_uses_sections()}
283      *
284      * Developers, note that if course format does use sections there should be defined a language
285      * string with the name 'sectionname' defining what the section relates to in the format, i.e.
286      * $string['sectionname'] = 'Topic';
287      * or
288      * $string['sectionname'] = 'Week';
289      *
290      * @return bool
291      */
292     public function uses_sections() {
293         return false;
294     }
296     /**
297      * Returns a list of sections used in the course
298      *
299      * This is a shortcut to get_fast_modinfo()->get_section_info_all()
300      * @see get_fast_modinfo()
301      * @see course_modinfo::get_section_info_all()
302      *
303      * @return array of section_info objects
304      */
305     public final function get_sections() {
306         if ($course = $this->get_course()) {
307             $modinfo = get_fast_modinfo($course);
308             return $modinfo->get_section_info_all();
309         }
310         return array();
311     }
313     /**
314      * Returns information about section used in course
315      *
316      * @param int|stdClass $section either section number (field course_section.section) or row from course_section table
317      * @param int $strictness
318      * @return section_info
319      */
320     public final function get_section($section, $strictness = IGNORE_MISSING) {
321         if (is_object($section)) {
322             $sectionnum = $section->section;
323         } else {
324             $sectionnum = $section;
325         }
326         $sections = $this->get_sections();
327         if (array_key_exists($sectionnum, $sections)) {
328             return $sections[$sectionnum];
329         }
330         if ($strictness == MUST_EXIST) {
331             throw new moodle_exception('sectionnotexist');
332         }
333         return null;
334     }
336     /**
337      * Returns the display name of the given section that the course prefers.
338      *
339      * @param int|stdClass $section Section object from database or just field course_sections.section
340      * @return Display name that the course format prefers, e.g. "Topic 2"
341      */
342     public function get_section_name($section) {
343         if (is_object($section)) {
344             $sectionnum = $section->section;
345         } else {
346             $sectionnum = $section;
347         }
348         return get_string('sectionname', 'format_'.$this->format) . ' ' . $sectionnum;
349     }
351     /**
352      * Returns the information about the ajax support in the given source format
353      *
354      * The returned object's property (boolean)capable indicates that
355      * the course format supports Moodle course ajax features.
356      *
357      * @return stdClass
358      */
359     public function supports_ajax() {
360         // no support by default
361         $ajaxsupport = new stdClass();
362         $ajaxsupport->capable = false;
363         return $ajaxsupport;
364     }
366     /**
367      * Custom action after section has been moved in AJAX mode
368      *
369      * Used in course/rest.php
370      *
371      * @return array This will be passed in ajax respose
372      */
373     public function ajax_section_move() {
374         return null;
375     }
377     /**
378      * The URL to use for the specified course (with section)
379      *
380      * Please note that course view page /course/view.php?id=COURSEID is hardcoded in many
381      * places in core and contributed modules. If course format wants to change the location
382      * of the view script, it is not enough to change just this function. Do not forget
383      * to add proper redirection.
384      *
385      * @param int|stdClass $section Section object from database or just field course_sections.section
386      *     if null the course view page is returned
387      * @param array $options options for view URL. At the moment core uses:
388      *     'navigation' (bool) if true and section has no separate page, the function returns null
389      *     'sr' (int) used by multipage formats to specify to which section to return
390      * @return null|moodle_url
391      */
392     public function get_view_url($section, $options = array()) {
393         $course = $this->get_course();
394         $url = new moodle_url('/course/view.php', array('id' => $course->id));
396         if (array_key_exists('sr', $options)) {
397             $sectionno = $options['sr'];
398         } else if (is_object($section)) {
399             $sectionno = $section->section;
400         } else {
401             $sectionno = $section;
402         }
403         if (!empty($options['navigation']) && $sectionno !== null) {
404             // by default assume that sections are never displayed on separate pages
405             return null;
406         }
407         if ($this->uses_sections() && $sectionno !== null) {
408             $url->set_anchor('section-'.$sectionno);
409         }
410         return $url;
411     }
413     /**
414      * Loads all of the course sections into the navigation
415      *
416      * This method is called from {@link global_navigation::load_course_sections()}
417      *
418      * By default the method {@link global_navigation::load_generic_course_sections()} is called
419      *
420      * When overwriting please note that navigationlib relies on using the correct values for
421      * arguments $type and $key in {@link navigation_node::add()}
422      *
423      * Example of code creating a section node:
424      * $sectionnode = $node->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);
425      * $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
426      *
427      * Example of code creating an activity node:
428      * $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
429      * if (global_navigation::module_extends_navigation($activity->modname)) {
430      *     $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
431      * } else {
432      *     $activitynode->nodetype = navigation_node::NODETYPE_LEAF;
433      * }
434      *
435      * Also note that if $navigation->includesectionnum is not null, the section with this relative
436      * number needs is expected to be loaded
437      *
438      * @param global_navigation $navigation
439      * @param navigation_node $node The course node within the navigation
440      */
441     public function extend_course_navigation($navigation, navigation_node $node) {
442         if ($course = $this->get_course()) {
443             $navigation->load_generic_course_sections($course, $node);
444         }
445         return array();
446     }
448     /**
449      * Returns the list of blocks to be automatically added for the newly created course
450      *
451      * @see blocks_add_default_course_blocks()
452      *
453      * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
454      *     each of values is an array of block names (for left and right side columns)
455      */
456     public function get_default_blocks() {
457         global $CFG;
458         if (!empty($CFG->defaultblocks)){
459             return blocks_parse_default_blocks_list($CFG->defaultblocks);
460         }
461         $blocknames = array(
462             BLOCK_POS_LEFT => array(),
463             BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
464         );
465         return $blocknames;
466     }
468     /**
469      * Returns the localised name of this course format plugin
470      *
471      * @return lang_string
472      */
473     public final function get_format_name() {
474         return new lang_string('pluginname', 'format_'.$this->get_format());
475     }
477     /**
478      * Definitions of the additional options that this course format uses for course
479      *
480      * This function may be called often, it should be as fast as possible.
481      * Avoid using get_string() method, use "new lang_string()" instead
482      * It is not recommended to use dynamic or course-dependant expressions here
483      * This function may be also called when course does not exist yet.
484      *
485      * Option names must be different from fields in the {course} talbe or any form elements on
486      * course edit form, it may even make sence to use special prefix for them.
487      *
488      * Each option must have the option name as a key and the array of properties as a value:
489      * 'default' - default value for this option (assumed null if not specified)
490      * 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.)
491      *
492      * Additional properties used by default implementation of
493      * {@link format_base::create_edit_form_elements()} (calls this method with $foreditform = true)
494      * 'label' - localised human-readable label for the edit form
495      * 'element_type' - type of the form element, default 'text'
496      * 'element_attributes' - additional attributes for the form element, these are 4th and further
497      *    arguments in the moodleform::addElement() method
498      * 'help' - string for help button. Note that if 'help' value is 'myoption' then the string with
499      *    the name 'myoption_help' must exist in the language file
500      * 'help_component' - language component to look for help string, by default this the component
501      *    for this course format
502      *
503      * This is an interface for creating simple form elements. If format plugin wants to use other
504      * methods such as disableIf, it can be done by overriding create_edit_form_elements().
505      *
506      * Course format options can be accessed as:
507      * $this->get_course()->OPTIONNAME (inside the format class)
508      * course_get_format($course)->get_course()->OPTIONNAME (outside of format class)
509      *
510      * All course options are returned by calling:
511      * $this->get_format_options();
512      *
513      * @param bool $foreditform
514      * @return array of options
515      */
516     public function course_format_options($foreditform = false) {
517         return array();
518     }
520     /**
521      * Definitions of the additional options that this course format uses for section
522      *
523      * See {@link format_base::course_format_options()} for return array definition.
524      *
525      * Additionally section format options may have property 'cache' set to true
526      * if this option needs to be cached in {@link get_fast_modinfo()}. The 'cache' property
527      * is recommended to be set only for fields used in {@link format_base::get_section_name()},
528      * {@link format_base::extend_course_navigation()} and {@link format_base::get_view_url()}
529      *
530      * For better performance cached options are recommended to have 'cachedefault' property
531      * Unlike 'default', 'cachedefault' should be static and not access get_config().
532      *
533      * Regardless of value of 'cache' all options are accessed in the code as
534      * $sectioninfo->OPTIONNAME
535      * where $sectioninfo is instance of section_info, returned by
536      * get_fast_modinfo($course)->get_section_info($sectionnum)
537      * or get_fast_modinfo($course)->get_section_info_all()
538      *
539      * All format options for particular section are returned by calling:
540      * $this->get_format_options($section);
541      *
542      * @param bool $foreditform
543      * @return array
544      */
545     public function section_format_options($foreditform = false) {
546         return array();
547     }
549     /**
550      * Returns the format options stored for this course or course section
551      *
552      * When overriding please note that this function is called from rebuild_course_cache()
553      * and section_info object, therefore using of get_fast_modinfo() and/or any function that
554      * accesses it may lead to recursion.
555      *
556      * @param null|int|stdClass|section_info $section if null the course format options will be returned
557      *     otherwise options for specified section will be returned. This can be either
558      *     section object or relative section number (field course_sections.section)
559      * @return array
560      */
561     public function get_format_options($section = null) {
562         global $DB;
563         if ($section === null) {
564             $options = $this->course_format_options();
565         } else {
566             $options = $this->section_format_options();
567         }
568         if (empty($options)) {
569             // there are no option for course/sections anyway, no need to go further
570             return array();
571         }
572         if ($section === null) {
573             // course format options will be returned
574             $sectionid = 0;
575         } else if ($this->courseid && isset($section->id)) {
576             // course section format options will be returned
577             $sectionid = $section->id;
578         } else if ($this->courseid && is_int($section) &&
579                 ($sectionobj = $DB->get_record('course_sections',
580                         array('section' => $section, 'course' => $this->courseid), 'id'))) {
581             // course section format options will be returned
582             $sectionid = $sectionobj->id;
583         } else {
584             // non-existing (yet) section was passed as an argument
585             // default format options for course section will be returned
586             $sectionid = -1;
587         }
588         if (!array_key_exists($sectionid, $this->formatoptions)) {
589             $this->formatoptions[$sectionid] = array();
590             // first fill with default values
591             foreach ($options as $optionname => $optionparams) {
592                 $this->formatoptions[$sectionid][$optionname] = null;
593                 if (array_key_exists('default', $optionparams)) {
594                     $this->formatoptions[$sectionid][$optionname] = $optionparams['default'];
595                 }
596             }
597             if ($this->courseid && $sectionid !== -1) {
598                 // overwrite the default options values with those stored in course_format_options table
599                 // nothing can be stored if we are interested in generic course ($this->courseid == 0)
600                 // or generic section ($sectionid === 0)
601                 $records = $DB->get_records('course_format_options',
602                         array('courseid' => $this->courseid,
603                               'format' => $this->format,
604                               'sectionid' => $sectionid
605                             ), '', 'id,name,value');
606                 foreach ($records as $record) {
607                     if (array_key_exists($record->name, $this->formatoptions[$sectionid])) {
608                         $value = $record->value;
609                         if ($value !== null && isset($options[$record->name]['type'])) {
610                             // this will convert string value to number if needed
611                             $value = clean_param($value, $options[$record->name]['type']);
612                         }
613                         $this->formatoptions[$sectionid][$record->name] = $value;
614                     }
615                 }
616             }
617         }
618         return $this->formatoptions[$sectionid];
619     }
621     /**
622      * Adds format options elements to the course/section edit form
623      *
624      * This function is called from {@link course_edit_form::definition_after_data()}
625      *
626      * @param MoodleQuickForm $mform form the elements are added to
627      * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form
628      * @return array array of references to the added form elements
629      */
630     public function create_edit_form_elements(&$mform, $forsection = false) {
631         $elements = array();
632         if ($forsection) {
633             $options = $this->section_format_options(true);
634         } else {
635             $options = $this->course_format_options(true);
636         }
637         foreach ($options as $optionname => $option) {
638             if (!isset($option['element_type'])) {
639                 $option['element_type'] = 'text';
640             }
641             $args = array($option['element_type'], $optionname, $option['label']);
642             if (!empty($option['element_attributes'])) {
643                 $args = array_merge($args, $option['element_attributes']);
644             }
645             $elements[] = call_user_func_array(array($mform, 'addElement'), $args);
646             if (isset($option['help'])) {
647                 $helpcomponent = 'format_'. $this->get_format();
648                 if (isset($option['help_component'])) {
649                     $helpcomponent = $option['help_component'];
650                 }
651                 $mform->addHelpButton($optionname, $option['help'], $helpcomponent);
652             }
653             if (isset($option['type'])) {
654                 $mform->setType($optionname, $option['type']);
655             }
656             if (is_null($mform->getElementValue($optionname)) && isset($option['default'])) {
657                 $mform->setDefault($optionname, $option['default']);
658             }
659         }
660         return $elements;
661     }
663     /**
664      * Override if you need to perform some extra validation of the format options
665      *
666      * @param array $data array of ("fieldname"=>value) of submitted data
667      * @param array $files array of uploaded files "element_name"=>tmp_file_path
668      * @param array $errors errors already discovered in edit form validation
669      * @return array of "element_name"=>"error_description" if there are errors,
670      *         or an empty array if everything is OK.
671      *         Do not repeat errors from $errors param here
672      */
673     public function edit_form_validation($data, $files, $errors) {
674         return array();
675     }
677     /**
678      * Updates format options for a course or section
679      *
680      * If $data does not contain property with the option name, the option will not be updated
681      *
682      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
683      * @param null|int null if these are options for course or section id (course_sections.id)
684      *     if these are options for section
685      * @return bool whether there were any changes to the options values
686      */
687     protected function update_format_options($data, $sectionid = null) {
688         global $DB;
689         if (!$sectionid) {
690             $allformatoptions = $this->course_format_options();
691             $sectionid = 0;
692         } else {
693             $allformatoptions = $this->section_format_options();
694         }
695         if (empty($allformatoptions)) {
696             // nothing to update anyway
697             return false;
698         }
699         $defaultoptions = array();
700         $cached = array();
701         foreach ($allformatoptions as $key => $option) {
702             $defaultoptions[$key] = null;
703             if (array_key_exists('default', $option)) {
704                 $defaultoptions[$key] = $option['default'];
705             }
706             $cached[$key] = ($sectionid === 0 || !empty($option['cache']));
707         }
708         $records = $DB->get_records('course_format_options',
709                 array('courseid' => $this->courseid,
710                       'format' => $this->format,
711                       'sectionid' => $sectionid
712                     ), '', 'name,id,value');
713         $changed = $needrebuild = false;
714         $data = (array)$data;
715         foreach ($defaultoptions as $key => $value) {
716             if (isset($records[$key])) {
717                 if (array_key_exists($key, $data) && $records[$key]->value !== $data[$key]) {
718                     $DB->set_field('course_format_options', 'value',
719                             $data[$key], array('id' => $records[$key]->id));
720                     $changed = true;
721                     $needrebuild = $needrebuild || $cached[$key];
722                 }
723             } else {
724                 if (array_key_exists($key, $data) && $data[$key] !== $value) {
725                     $newvalue = $data[$key];
726                     $changed = true;
727                     $needrebuild = $needrebuild || $cached[$key];
728                 } else {
729                     $newvalue = $value;
730                     // we still insert entry in DB but there are no changes from user point of
731                     // view and no need to call rebuild_course_cache()
732                 }
733                 $DB->insert_record('course_format_options', array(
734                     'courseid' => $this->courseid,
735                     'format' => $this->format,
736                     'sectionid' => $sectionid,
737                     'name' => $key,
738                     'value' => $newvalue
739                 ));
740             }
741         }
742         if ($needrebuild) {
743             rebuild_course_cache($this->courseid, true);
744         }
745         if ($changed) {
746             // reset internal caches
747             if (!$sectionid) {
748                 $this->course = false;
749             }
750             unset($this->formatoptions[$sectionid]);
751         }
752         return $changed;
753     }
755     /**
756      * Updates format options for a course
757      *
758      * If $data does not contain property with the option name, the option will not be updated
759      *
760      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
761      * @param stdClass $oldcourse if this function is called from {@link update_course()}
762      *     this object contains information about the course before update
763      * @return bool whether there were any changes to the options values
764      */
765     public function update_course_format_options($data, $oldcourse = null) {
766         return $this->update_format_options($data);
767     }
769     /**
770      * Updates format options for a section
771      *
772      * Section id is expected in $data->id (or $data['id'])
773      * If $data does not contain property with the option name, the option will not be updated
774      *
775      * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
776      * @return bool whether there were any changes to the options values
777      */
778     public function update_section_format_options($data) {
779         $data = (array)$data;
780         return $this->update_format_options($data, $data['id']);
781     }
783     /**
784      * Return an instance of moodleform to edit a specified section
785      *
786      * Default implementation returns instance of editsection_form that automatically adds
787      * additional fields defined in {@link format_base::section_format_options()}
788      *
789      * Format plugins may extend editsection_form if they want to have custom edit section form.
790      *
791      * @param mixed $action the action attribute for the form. If empty defaults to auto detect the
792      *              current url. If a moodle_url object then outputs params as hidden variables.
793      * @param array $customdata the array with custom data to be passed to the form
794      *     /course/editsection.php passes section_info object in 'cs' field
795      *     for filling availability fields
796      * @return moodleform
797      */
798     public function editsection_form($action, $customdata = array()) {
799         global $CFG;
800         require_once($CFG->dirroot. '/course/editsection_form.php');
801         $context = context_course::instance($this->courseid);
802         if (!array_key_exists('course', $customdata)) {
803             $customdata['course'] = $this->get_course();
804         }
805         return new editsection_form($action, $customdata);
806     }
808     /**
809      * Allows course format to execute code on moodle_page::set_course()
810      *
811      * @param moodle_page $page instance of page calling set_course
812      */
813     public function page_set_course(moodle_page $page) {
814     }
816     /**
817      * Allows course format to execute code on moodle_page::set_cm()
818      *
819      * Current module can be accessed as $page->cm (returns instance of cm_info)
820      *
821      * @param moodle_page $page instance of page calling set_cm
822      */
823     public function page_set_cm(moodle_page $page) {
824     }
826     /**
827      * Course-specific information to be output on any course page (usually above navigation bar)
828      *
829      * Example of usage:
830      * define
831      * class format_FORMATNAME_XXX implements renderable {}
832      *
833      * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:
834      * class format_FORMATNAME_renderer extends plugin_renderer_base {
835      *     protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {
836      *         return html_writer::tag('div', 'This is my header/footer');
837      *     }
838      * }
839      *
840      * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from
841      * plugin renderer will be called
842      *
843      * @return null|renderable null for no output or object with data for plugin renderer
844      */
845     public function course_header() {
846         return null;
847     }
849     /**
850      * Course-specific information to be output on any course page (usually in the beginning of
851      * standard footer)
852      *
853      * See {@link format_base::course_header()} for usage
854      *
855      * @return null|renderable null for no output or object with data for plugin renderer
856      */
857     public function course_footer() {
858         return null;
859     }
861     /**
862      * Course-specific information to be output immediately above content on any course page
863      *
864      * See {@link format_base::course_header()} for usage
865      *
866      * @return null|renderable null for no output or object with data for plugin renderer
867      */
868     public function course_content_header() {
869         return null;
870     }
872     /**
873      * Course-specific information to be output immediately below content on any course page
874      *
875      * See {@link format_base::course_header()} for usage
876      *
877      * @return null|renderable null for no output or object with data for plugin renderer
878      */
879     public function course_content_footer() {
880         return null;
881     }
883     /**
884      * Returns instance of page renderer used by this plugin
885      *
886      * @param moodle_page $page
887      * @return renderer_base
888      */
889     public function get_renderer(moodle_page $page) {
890         return $page->get_renderer('format_'. $this->get_format());
891     }
893     /**
894      * Returns true if the specified section is current
895      *
896      * By default we analyze $course->marker
897      *
898      * @param int|stdClass|section_info $section
899      * @return bool
900      */
901     public function is_section_current($section) {
902         if (is_object($section)) {
903             $sectionnum = $section->section;
904         } else {
905             $sectionnum = $section;
906         }
907         return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);
908     }
911 /**
912  * Pseudo course format used for the site main page
913  *
914  * @package    core_course
915  * @copyright  2012 Marina Glancy
916  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
917  */
918 class format_site extends format_base {
920     /**
921      * Returns the display name of the given section that the course prefers.
922      *
923      * @param int|stdClass $section Section object from database or just field section.section
924      * @return Display name that the course format prefers, e.g. "Topic 2"
925      */
926     function get_section_name($section) {
927         return get_string('site');
928     }
930     /**
931      * For this fake course referring to the whole site, the site homepage is always returned
932      * regardless of arguments
933      *
934      * @param int|stdClass $section
935      * @param array $options
936      * @return null|moodle_url
937      */
938     public function get_view_url($section, $options = array()) {
939         return new moodle_url('/');
940     }
942     /**
943      * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed
944      *
945      * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
946      *     each of values is an array of block names (for left and right side columns)
947      */
948     public function get_default_blocks() {
949         return blocks_get_default_site_course_blocks();
950     }
952     /**
953      * Definitions of the additional options that site uses
954      *
955      * @param bool $foreditform
956      * @return array of options
957      */
958     public function course_format_options($foreditform = false) {
959         static $courseformatoptions = false;
960         if ($courseformatoptions === false) {
961             $courseformatoptions = array(
962                 'numsections' => array(
963                     'default' => 1,
964                     'type' => PARAM_INT,
965                 ),
966             );
967         }
968         return $courseformatoptions;
969     }