MDL-34921 course/format: Fix unwanted redirects from Site home
[moodle.git] / course / format / lib.php
CommitLineData
ee7084e9
MG
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/>.
16
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 */
24
25defined('MOODLE_INTERNAL') || die;
26
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 */
34function course_get_format($courseorid) {
35 return format_base::instance($courseorid);
36}
37
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 */
63abstract 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;
fc79ede5
MG
71 /** @var array caches format options, please use {@link format_base::get_format_options()} */
72 protected $formatoptions = array();
ee7084e9
MG
73 /** @var array cached instances */
74 private static $instances = array();
6382060d
TH
75 /** @var array plugin name => class name. */
76 private static $classesforformat = array('site' => 'site');
ee7084e9
MG
77
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 }
91
92 /**
76ab3fa1 93 * Validates that course format exists and enabled and returns either itself or default format
ee7084e9
MG
94 *
95 * @param string $format
96 * @return string
97 */
76ab3fa1 98 protected static final function get_format_or_default($format) {
6382060d
TH
99 if (array_key_exists($format, self::$classesforformat)) {
100 return self::$classesforformat[$format];
ee7084e9 101 }
6382060d 102
3776335c 103 $plugins = get_sorted_course_formats();
6382060d
TH
104 foreach ($plugins as $plugin) {
105 self::$classesforformat[$plugin] = $plugin;
106 }
107
108 if (array_key_exists($format, self::$classesforformat)) {
109 return self::$classesforformat[$format];
ee7084e9 110 }
6382060d 111
daad1b4b
MG
112 if (PHPUNIT_TEST && class_exists('format_' . $format)) {
113 // Allow unittests to use non-existing course formats.
114 return $format;
115 }
116
ee7084e9 117 // Else return default format
3776335c
MG
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 }
6382060d
TH
123 debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
124
125 self::$classesforformat[$format] = $defaultformat;
ee7084e9
MG
126 return $defaultformat;
127 }
128
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])) {
bd3b3bba 142 $plugins = core_component::get_plugin_list('format');
76ab3fa1 143 $usedformat = self::get_format_or_default($format);
daad1b4b 144 if (isset($plugins[$usedformat]) && file_exists($plugins[$usedformat].'/lib.php')) {
76ab3fa1 145 require_once($plugins[$usedformat].'/lib.php');
ee7084e9
MG
146 }
147 $classnames[$format] = 'format_'. $usedformat;
148 if (!class_exists($classnames[$format])) {
76ab3fa1 149 require_once($CFG->dirroot.'/course/format/formatlegacy.php');
ee7084e9
MG
150 $classnames[$format] = 'format_legacy';
151 }
152 }
153 return $classnames[$format];
154 }
155
156 /**
157 * Returns an instance of the class
158 *
76ab3fa1 159 * @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances
ee7084e9
MG
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) {
76ab3fa1
MG
170 $formats = array_keys(self::$instances[$courseid]);
171 $format = reset($formats);
ee7084e9
MG
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)) {
76ab3fa1 178 $courseid = clean_param($courseorid->id, PARAM_INT);
ee7084e9
MG
179 } else {
180 $courseid = 0;
181 }
182 }
183 // validate that format exists and enabled, use default otherwise
76ab3fa1 184 $format = self::get_format_or_default($format);
ee7084e9
MG
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 }
191
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;
fc79ede5 204 self::$instances[$courseid][$format]->formatoptions = array();
ee7084e9
MG
205 }
206 unset(self::$instances[$courseid]);
207 }
208 } else {
209 self::$instances = array();
210 }
211 }
212
213 /**
214 * Returns the format name used by this course
215 *
216 * @return string
217 */
76ab3fa1 218 public final function get_format() {
ee7084e9
MG
219 return $this->format;
220 }
221
222 /**
223 * Returns id of the course (0 if course is not specified)
224 *
225 * @return int
226 */
76ab3fa1 227 public final function get_courseid() {
ee7084e9
MG
228 return $this->courseid;
229 }
230
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) {
a2602457 243 $this->course = get_course($this->courseid);
fc79ede5 244 $options = $this->get_format_options();
7456c967 245 $dbcoursecolumns = null;
fc79ede5 246 foreach ($options as $optionname => $optionvalue) {
7456c967
MG
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 }
fc79ede5 258 }
7456c967 259 $this->course->$optionname = $optionvalue;
fc79ede5 260 }
ee7084e9
MG
261 }
262 return $this->course;
263 }
264
d9203fb7
FM
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
5bcfd504 277 * @since Moodle 2.6
d9203fb7
FM
278 */
279 public function has_view_page() {
280 return true;
281 }
282
ee7084e9
MG
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 }
300
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 }
317
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 }
340
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 }
76ab3fa1 355
ee7084e9
MG
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.
ee7084e9
MG
361 *
362 * @return stdClass
363 */
364 public function supports_ajax() {
365 // no support by default
366 $ajaxsupport = new stdClass();
367 $ajaxsupport->capable = false;
ee7084e9
MG
368 return $ajaxsupport;
369 }
370
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 }
381
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()) {
53623a70 398 global $CFG;
ee7084e9
MG
399 $course = $this->get_course();
400 $url = new moodle_url('/course/view.php', array('id' => $course->id));
1872a0f2 401
ee7084e9 402 if (array_key_exists('sr', $options)) {
b5cf83f0
MG
403 $sectionno = $options['sr'];
404 } else if (is_object($section)) {
ee7084e9
MG
405 $sectionno = $section->section;
406 } else {
407 $sectionno = $section;
408 }
53623a70 409 if (empty($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) {
b5cf83f0
MG
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);
ee7084e9
MG
415 }
416 return $url;
417 }
418
419 /**
420 * Loads all of the course sections into the navigation
421 *
e010b850
MG
422 * This method is called from {@link global_navigation::load_course_sections()}
423 *
ee7084e9
MG
424 * By default the method {@link global_navigation::load_generic_course_sections()} is called
425 *
e010b850
MG
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 *
ee7084e9
MG
444 * @param global_navigation $navigation
445 * @param navigation_node $node The course node within the navigation
ee7084e9 446 */
e010b850 447 public function extend_course_navigation($navigation, navigation_node $node) {
ee7084e9 448 if ($course = $this->get_course()) {
e010b850 449 $navigation->load_generic_course_sections($course, $node);
ee7084e9
MG
450 }
451 return array();
452 }
a49e2ea7
MG
453
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 }
fc79ede5
MG
473
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 }
482
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:
aea2e3c3 495 * 'default' - default value for this option (assumed null if not specified)
fc79ede5 496 * 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.)
aea2e3c3
MG
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
fc79ede5
MG
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 *
fc79ede5
MG
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 *
aea2e3c3
MG
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 *
fc79ede5
MG
519 * @param bool $foreditform
520 * @return array of options
521 */
522 public function course_format_options($foreditform = false) {
523 return array();
524 }
525
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 *
aea2e3c3
MG
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 *
fc79ede5
MG
548 * @param bool $foreditform
549 * @return array
550 */
551 public function section_format_options($foreditform = false) {
552 return array();
553 }
554
555 /**
556 * Returns the format options stored for this course or course section
557 *
aea2e3c3
MG
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 *
fc79ede5
MG
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;
aea2e3c3
MG
584 } else if ($this->courseid && is_int($section) &&
585 ($sectionobj = $DB->get_record('course_sections',
6382c482 586 array('section' => $section, 'course' => $this->courseid), 'id'))) {
fc79ede5
MG
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 }
626
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 *
92d38668 632 * @param MoodleQuickForm $mform form the elements are added to
fc79ede5
MG
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 }
6b02cc4f 651 $elements[] = call_user_func_array(array($mform, 'addElement'), $args);
fc79ede5
MG
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 }
668
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 }
682
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 }
760
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 }
774
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 }
788
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 }
4b5a6b13 813
695705f5
MG
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 }
821
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 }
5ef23705
MG
831
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 }
854
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 }
866
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 }
877
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 }
888
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 }
081c8f7f
MG
898
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 }
daad1b4b
MG
915
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 }
ca9cae84
MG
938
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 }
953
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 }
977
978 if (!$forcedeleteifnotempty && !empty($section->sequence)) {
979 return false;
980 }
981
982 $course = $this->get_course();
983
984 // Remove the marker if it points to this section.
985 if ($section->section == $course->marker) {
986 course_set_marker($course->id, 0);
987 }
988
989 $lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
990 WHERE course = ?', array($course->id));
991
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);
996
997 // Move the section to the end.
998 move_section_to($course, $section->section, $lastsection, true);
999
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 }
1004
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);
1009
1010 // Descrease 'numsections' if needed.
1011 if ($decreasenumsections) {
1012 $this->update_course_format_options(array('numsections' => $course->numsections - 1));
1013 }
1014
1015 return true;
1016 }
ee7084e9
MG
1017}
1018
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 */
1026class format_site extends format_base {
1027
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');
76ab3fa1 1036 }
ee7084e9
MG
1037
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()) {
68255608 1047 return new moodle_url('/', array('redirect' => 0));
ee7084e9 1048 }
ee7084e9 1049
b5cf83f0
MG
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 }
1059
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 }
1078}