Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 25 defined('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 */ 34 function 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 */ 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'); 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 /** 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 global $CFG; 100 require_once($CFG->dirroot . '/course/lib.php'); 101 102 if (array_key_exists($format, self::$classesforformat)) { 103 return self::$classesforformat[$format]; 104 } 105 106 $plugins = get_sorted_course_formats(); 107 foreach ($plugins as $plugin) { 108 self::$classesforformat[$plugin] = $plugin; 109 } 110 111 if (array_key_exists($format, self::$classesforformat)) { 112 return self::$classesforformat[$format]; 113 } 114 115 if (PHPUNIT_TEST && class_exists('format_' . $format)) { 116 // Allow unittests to use non-existing course formats. 117 return $format; 118 } 119 120 // Else return default format 121 $defaultformat = get_config('moodlecourse', 'format'); 122 if (!in_array($defaultformat, $plugins)) { 123 // when default format is not set correctly, use the first available format 124 $defaultformat = reset($plugins); 125 } 126 debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER); 127 128 self::$classesforformat[$format] = $defaultformat; 129 return $defaultformat; 130 } 131 132 /** 133 * Get class name for the format 134 * 135 * If course format xxx does not declare class format_xxx, format_legacy will be returned. 136 * This function also includes lib.php file from corresponding format plugin 137 * 138 * @param string $format 139 * @return string 140 */ 141 protected static final function get_class_name($format) { 142 global $CFG; 143 static $classnames = array('site' => 'format_site'); 144 if (!isset($classnames[$format])) { 145 $plugins = core_component::get_plugin_list('format'); 146 $usedformat = self::get_format_or_default($format); 147 if (isset($plugins[$usedformat]) && file_exists($plugins[$usedformat].'/lib.php')) { 148 require_once($plugins[$usedformat].'/lib.php'); 149 } 150 $classnames[$format] = 'format_'. $usedformat; 151 if (!class_exists($classnames[$format])) { 152 require_once($CFG->dirroot.'/course/format/formatlegacy.php'); 153 $classnames[$format] = 'format_legacy'; 154 } 155 } 156 return $classnames[$format]; 157 } 158 159 /** 160 * Returns an instance of the class 161 * 162 * @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances 163 * 164 * @param int|stdClass $courseorid either course id or 165 * an object that has the property 'format' and may contain property 'id' 166 * @return format_base 167 */ 168 public static final function instance($courseorid) { 169 global $DB; 170 if (!is_object($courseorid)) { 171 $courseid = (int)$courseorid; 172 if ($courseid && isset(self::$instances[$courseid]) && count(self::$instances[$courseid]) == 1) { 173 $formats = array_keys(self::$instances[$courseid]); 174 $format = reset($formats); 175 } else { 176 $format = $DB->get_field('course', 'format', array('id' => $courseid), MUST_EXIST); 177 } 178 } else { 179 $format = $courseorid->format; 180 if (isset($courseorid->id)) { 181 $courseid = clean_param($courseorid->id, PARAM_INT); 182 } else { 183 $courseid = 0; 184 } 185 } 186 // validate that format exists and enabled, use default otherwise 187 $format = self::get_format_or_default($format); 188 if (!isset(self::$instances[$courseid][$format])) { 189 $classname = self::get_class_name($format); 190 self::$instances[$courseid][$format] = new $classname($format, $courseid); 191 } 192 return self::$instances[$courseid][$format]; 193 } 194 195 /** 196 * Resets cache for the course (or all caches) 197 * To be called from {@link rebuild_course_cache()} 198 * 199 * @param int $courseid 200 */ 201 public static final function reset_course_cache($courseid = 0) { 202 if ($courseid) { 203 if (isset(self::$instances[$courseid])) { 204 foreach (self::$instances[$courseid] as $format => $object) { 205 // in case somebody keeps the reference to course format object 206 self::$instances[$courseid][$format]->course = false; 207 self::$instances[$courseid][$format]->formatoptions = array(); 208 } 209 unset(self::$instances[$courseid]); 210 } 211 } else { 212 self::$instances = array(); 213 } 214 } 215 216 /** 217 * Returns the format name used by this course 218 * 219 * @return string 220 */ 221 public final function get_format() { 222 return $this->format; 223 } 224 225 /** 226 * Returns id of the course (0 if course is not specified) 227 * 228 * @return int 229 */ 230 public final function get_courseid() { 231 return $this->courseid; 232 } 233 234 /** 235 * Returns a record from course database table plus additional fields 236 * that course format defines 237 * 238 * @return stdClass 239 */ 240 public function get_course() { 241 global $DB; 242 if (!$this->courseid) { 243 return null; 244 } 245 if ($this->course === false) { 246 $this->course = get_course($this->courseid); 247 $options = $this->get_format_options(); 248 $dbcoursecolumns = null; 249 foreach ($options as $optionname => $optionvalue) { 250 if (isset($this->course->$optionname)) { 251 // Course format options must not have the same names as existing columns in db table "course". 252 if (!isset($dbcoursecolumns)) { 253 $dbcoursecolumns = $DB->get_columns('course'); 254 } 255 if (isset($dbcoursecolumns[$optionname])) { 256 debugging('The option name '.$optionname.' in course format '.$this->format. 257 ' is invalid because the field with the same name exists in {course} table', 258 DEBUG_DEVELOPER); 259 continue; 260 } 261 } 262 $this->course->$optionname = $optionvalue; 263 } 264 } 265 return $this->course; 266 } 267 268 /** 269 * Method used in the rendered and during backup instead of legacy 'numsections' 270 * 271 * Default renderer will treat sections with sectionnumber greater that the value returned by this 272 * method as "orphaned" and not display them on the course page unless in editing mode. 273 * Backup will store this value as 'numsections'. 274 * 275 * This method ensures that 3rd party course format plugins that still use 'numsections' continue to 276 * work but at the same time we no longer expect formats to have 'numsections' property. 277 * 278 * @return int 279 */ 280 public function get_last_section_number() { 281 $course = $this->get_course(); 282 if (isset($course->numsections)) { 283 return $course->numsections; 284 } 285 $modinfo = get_fast_modinfo($course); 286 $sections = $modinfo->get_section_info_all(); 287 return (int)max(array_keys($sections)); 288 } 289 290 /** 291 * Method used to get the maximum number of sections for this course format. 292 * @return int 293 */ 294 public function get_max_sections() { 295 $maxsections = get_config('moodlecourse', 'maxsections'); 296 if (!isset($maxsections) || !is_numeric($maxsections)) { 297 $maxsections = 52; 298 } 299 return $maxsections; 300 } 301 302 /** 303 * Returns true if the course has a front page. 304 * 305 * This function is called to determine if the course has a view page, whether or not 306 * it contains a listing of activities. It can be useful to set this to false when the course 307 * format has only one activity and ignores the course page. Or if there are multiple 308 * activities but no page to see the centralised information. 309 * 310 * Initially this was created to know if forms should add a button to return to the course page. 311 * So if 'Return to course' does not make sense in your format your should probably return false. 312 * 313 * @return boolean 314 * @since Moodle 2.6 315 */ 316 public function has_view_page() { 317 return true; 318 } 319 320 /** 321 * Returns true if this course format uses sections 322 * 323 * This function may be called without specifying the course id 324 * i.e. in {@link course_format_uses_sections()} 325 * 326 * Developers, note that if course format does use sections there should be defined a language 327 * string with the name 'sectionname' defining what the section relates to in the format, i.e. 328 * $string['sectionname'] = 'Topic'; 329 * or 330 * $string['sectionname'] = 'Week'; 331 * 332 * @return bool 333 */ 334 public function uses_sections() { 335 return false; 336 } 337 338 /** 339 * Returns a list of sections used in the course 340 * 341 * This is a shortcut to get_fast_modinfo()->get_section_info_all() 342 * @see get_fast_modinfo() 343 * @see course_modinfo::get_section_info_all() 344 * 345 * @return array of section_info objects 346 */ 347 public final function get_sections() { 348 if ($course = $this->get_course()) { 349 $modinfo = get_fast_modinfo($course); 350 return $modinfo->get_section_info_all(); 351 } 352 return array(); 353 } 354 355 /** 356 * Returns information about section used in course 357 * 358 * @param int|stdClass $section either section number (field course_section.section) or row from course_section table 359 * @param int $strictness 360 * @return section_info 361 */ 362 public final function get_section($section, $strictness = IGNORE_MISSING) { 363 if (is_object($section)) { 364 $sectionnum = $section->section; 365 } else { 366 $sectionnum = $section; 367 } 368 $sections = $this->get_sections(); 369 if (array_key_exists($sectionnum, $sections)) { 370 return $sections[$sectionnum]; 371 } 372 if ($strictness == MUST_EXIST) { 373 throw new moodle_exception('sectionnotexist'); 374 } 375 return null; 376 } 377 378 /** 379 * Returns the display name of the given section that the course prefers. 380 * 381 * @param int|stdClass $section Section object from database or just field course_sections.section 382 * @return Display name that the course format prefers, e.g. "Topic 2" 383 */ 384 public function get_section_name($section) { 385 if (is_object($section)) { 386 $sectionnum = $section->section; 387 } else { 388 $sectionnum = $section; 389 } 390 391 if (get_string_manager()->string_exists('sectionname', 'format_' . $this->format)) { 392 return get_string('sectionname', 'format_' . $this->format) . ' ' . $sectionnum; 393 } 394 395 // Return an empty string if there's no available section name string for the given format. 396 return ''; 397 } 398 399 /** 400 * Returns the default section using format_base's implementation of get_section_name. 401 * 402 * @param int|stdClass $section Section object from database or just field course_sections section 403 * @return string The default value for the section name based on the given course format. 404 */ 405 public function get_default_section_name($section) { 406 return self::get_section_name($section); 407 } 408 409 /** 410 * Returns the information about the ajax support in the given source format 411 * 412 * The returned object's property (boolean)capable indicates that 413 * the course format supports Moodle course ajax features. 414 * 415 * @return stdClass 416 */ 417 public function supports_ajax() { 418 // no support by default 419 $ajaxsupport = new stdClass(); 420 $ajaxsupport->capable = false; 421 return $ajaxsupport; 422 } 423 424 /** 425 * Custom action after section has been moved in AJAX mode 426 * 427 * Used in course/rest.php 428 * 429 * @return array This will be passed in ajax respose 430 */ 431 public function ajax_section_move() { 432 return null; 433 } 434 435 /** 436 * The URL to use for the specified course (with section) 437 * 438 * Please note that course view page /course/view.php?id=COURSEID is hardcoded in many 439 * places in core and contributed modules. If course format wants to change the location 440 * of the view script, it is not enough to change just this function. Do not forget 441 * to add proper redirection. 442 * 443 * @param int|stdClass $section Section object from database or just field course_sections.section 444 * if null the course view page is returned 445 * @param array $options options for view URL. At the moment core uses: 446 * 'navigation' (bool) if true and section has no separate page, the function returns null 447 * 'sr' (int) used by multipage formats to specify to which section to return 448 * @return null|moodle_url 449 */ 450 public function get_view_url($section, $options = array()) { 451 global $CFG; 452 $course = $this->get_course(); 453 $url = new moodle_url('/course/view.php', array('id' => $course->id)); 454 455 if (array_key_exists('sr', $options)) { 456 $sectionno = $options['sr']; 457 } else if (is_object($section)) { 458 $sectionno = $section->section; 459 } else { 460 $sectionno = $section; 461 } 462 if (empty($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) { 463 // by default assume that sections are never displayed on separate pages 464 return null; 465 } 466 if ($this->uses_sections() && $sectionno !== null) { 467 $url->set_anchor('section-'.$sectionno); 468 } 469 return $url; 470 } 471 472 /** 473 * Loads all of the course sections into the navigation 474 * 475 * This method is called from {@link global_navigation::load_course_sections()} 476 * 477 * By default the method {@link global_navigation::load_generic_course_sections()} is called 478 * 479 * When overwriting please note that navigationlib relies on using the correct values for 480 * arguments $type and $key in {@link navigation_node::add()} 481 * 482 * Example of code creating a section node: 483 * $sectionnode = $node->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id); 484 * $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH; 485 * 486 * Example of code creating an activity node: 487 * $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon); 488 * if (global_navigation::module_extends_navigation($activity->modname)) { 489 * $activitynode->nodetype = navigation_node::NODETYPE_BRANCH; 490 * } else { 491 * $activitynode->nodetype = navigation_node::NODETYPE_LEAF; 492 * } 493 * 494 * Also note that if $navigation->includesectionnum is not null, the section with this relative 495 * number needs is expected to be loaded 496 * 497 * @param global_navigation $navigation 498 * @param navigation_node $node The course node within the navigation 499 */ 500 public function extend_course_navigation($navigation, navigation_node $node) { 501 if ($course = $this->get_course()) { 502 $navigation->load_generic_course_sections($course, $node); 503 } 504 return array(); 505 } 506 507 /** 508 * Returns the list of blocks to be automatically added for the newly created course 509 * 510 * @see blocks_add_default_course_blocks() 511 * 512 * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT 513 * each of values is an array of block names (for left and right side columns) 514 */ 515 public function get_default_blocks() { 516 global $CFG; 517 if (isset($CFG->defaultblocks)) { 518 return blocks_parse_default_blocks_list($CFG->defaultblocks); 519 } 520 $blocknames = array( 521 BLOCK_POS_LEFT => array(), 522 BLOCK_POS_RIGHT => array() 523 ); 524 return $blocknames; 525 } 526 527 /** 528 * Returns the localised name of this course format plugin 529 * 530 * @return lang_string 531 */ 532 public final function get_format_name() { 533 return new lang_string('pluginname', 'format_'.$this->get_format()); 534 } 535 536 /** 537 * Definitions of the additional options that this course format uses for course 538 * 539 * This function may be called often, it should be as fast as possible. 540 * Avoid using get_string() method, use "new lang_string()" instead 541 * It is not recommended to use dynamic or course-dependant expressions here 542 * This function may be also called when course does not exist yet. 543 * 544 * Option names must be different from fields in the {course} talbe or any form elements on 545 * course edit form, it may even make sence to use special prefix for them. 546 * 547 * Each option must have the option name as a key and the array of properties as a value: 548 * 'default' - default value for this option (assumed null if not specified) 549 * 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.) 550 * 551 * Additional properties used by default implementation of 552 * {@link format_base::create_edit_form_elements()} (calls this method with $foreditform = true) 553 * 'label' - localised human-readable label for the edit form 554 * 'element_type' - type of the form element, default 'text' 555 * 'element_attributes' - additional attributes for the form element, these are 4th and further 556 * arguments in the moodleform::addElement() method 557 * 'help' - string for help button. Note that if 'help' value is 'myoption' then the string with 558 * the name 'myoption_help' must exist in the language file 559 * 'help_component' - language component to look for help string, by default this the component 560 * for this course format 561 * 562 * This is an interface for creating simple form elements. If format plugin wants to use other 563 * methods such as disableIf, it can be done by overriding create_edit_form_elements(). 564 * 565 * Course format options can be accessed as: 566 * $this->get_course()->OPTIONNAME (inside the format class) 567 * course_get_format($course)->get_course()->OPTIONNAME (outside of format class) 568 * 569 * All course options are returned by calling: 570 * $this->get_format_options(); 571 * 572 * @param bool $foreditform 573 * @return array of options 574 */ 575 public function course_format_options($foreditform = false) { 576 return array(); 577 } 578 579 /** 580 * Definitions of the additional options that this course format uses for section 581 * 582 * See {@link format_base::course_format_options()} for return array definition. 583 * 584 * Additionally section format options may have property 'cache' set to true 585 * if this option needs to be cached in {@link get_fast_modinfo()}. The 'cache' property 586 * is recommended to be set only for fields used in {@link format_base::get_section_name()}, 587 * {@link format_base::extend_course_navigation()} and {@link format_base::get_view_url()} 588 * 589 * For better performance cached options are recommended to have 'cachedefault' property 590 * Unlike 'default', 'cachedefault' should be static and not access get_config(). 591 * 592 * Regardless of value of 'cache' all options are accessed in the code as 593 * $sectioninfo->OPTIONNAME 594 * where $sectioninfo is instance of section_info, returned by 595 * get_fast_modinfo($course)->get_section_info($sectionnum) 596 * or get_fast_modinfo($course)->get_section_info_all() 597 * 598 * All format options for particular section are returned by calling: 599 * $this->get_format_options($section); 600 * 601 * @param bool $foreditform 602 * @return array 603 */ 604 public function section_format_options($foreditform = false) { 605 return array(); 606 } 607 608 /** 609 * Returns the format options stored for this course or course section 610 * 611 * When overriding please note that this function is called from rebuild_course_cache() 612 * and section_info object, therefore using of get_fast_modinfo() and/or any function that 613 * accesses it may lead to recursion. 614 * 615 * @param null|int|stdClass|section_info $section if null the course format options will be returned 616 * otherwise options for specified section will be returned. This can be either 617 * section object or relative section number (field course_sections.section) 618 * @return array 619 */ 620 public function get_format_options($section = null) { 621 global $DB; 622 if ($section === null) { 623 $options = $this->course_format_options(); 624 } else { 625 $options = $this->section_format_options(); 626 } 627 if (empty($options)) { 628 // there are no option for course/sections anyway, no need to go further 629 return array(); 630 } 631 if ($section === null) { 632 // course format options will be returned 633 $sectionid = 0; 634 } else if ($this->courseid && isset($section->id)) { 635 // course section format options will be returned 636 $sectionid = $section->id; 637 } else if ($this->courseid && is_int($section) && 638 ($sectionobj = $DB->get_record('course_sections', 639 array('section' => $section, 'course' => $this->courseid), 'id'))) { 640 // course section format options will be returned 641 $sectionid = $sectionobj->id; 642 } else { 643 // non-existing (yet) section was passed as an argument 644 // default format options for course section will be returned 645 $sectionid = -1; 646 } 647 if (!array_key_exists($sectionid, $this->formatoptions)) { 648 $this->formatoptions[$sectionid] = array(); 649 // first fill with default values 650 foreach ($options as $optionname => $optionparams) { 651 $this->formatoptions[$sectionid][$optionname] = null; 652 if (array_key_exists('default', $optionparams)) { 653 $this->formatoptions[$sectionid][$optionname] = $optionparams['default']; 654 } 655 } 656 if ($this->courseid && $sectionid !== -1) { 657 // overwrite the default options values with those stored in course_format_options table 658 // nothing can be stored if we are interested in generic course ($this->courseid == 0) 659 // or generic section ($sectionid === 0) 660 $records = $DB->get_records('course_format_options', 661 array('courseid' => $this->courseid, 662 'format' => $this->format, 663 'sectionid' => $sectionid 664 ), '', 'id,name,value'); 665 666 $indexedrecords = []; 667 foreach ($records as $record) { 668 $indexedrecords[$record->name] = $record->value; 669 } 670 foreach ($options as $optionname => $option) { 671 contract_value($this->formatoptions[$sectionid], $indexedrecords, $option, $optionname); 672 } 673 } 674 } 675 return $this->formatoptions[$sectionid]; 676 } 677 678 /** 679 * Adds format options elements to the course/section edit form 680 * 681 * This function is called from {@link course_edit_form::definition_after_data()} 682 * 683 * @param MoodleQuickForm $mform form the elements are added to 684 * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form 685 * @return array array of references to the added form elements 686 */ 687 public function create_edit_form_elements(&$mform, $forsection = false) { 688 $elements = array(); 689 if ($forsection) { 690 $options = $this->section_format_options(true); 691 } else { 692 $options = $this->course_format_options(true); 693 } 694 foreach ($options as $optionname => $option) { 695 if (!isset($option['element_type'])) { 696 $option['element_type'] = 'text'; 697 } 698 $args = array($option['element_type'], $optionname, $option['label']); 699 if (!empty($option['element_attributes'])) { 700 $args = array_merge($args, $option['element_attributes']); 701 } 702 $elements[] = call_user_func_array(array($mform, 'addElement'), $args); 703 if (isset($option['help'])) { 704 $helpcomponent = 'format_'. $this->get_format(); 705 if (isset($option['help_component'])) { 706 $helpcomponent = $option['help_component']; 707 } 708 $mform->addHelpButton($optionname, $option['help'], $helpcomponent); 709 } 710 if (isset($option['type'])) { 711 $mform->setType($optionname, $option['type']); 712 } 713 if (isset($option['default']) && !array_key_exists($optionname, $mform->_defaultValues)) { 714 // Set defaults for the elements in the form. 715 // Since we call this method after set_data() make sure that we don't override what was already set. 716 $mform->setDefault($optionname, $option['default']); 717 } 718 } 719 720 if (!$forsection && empty($this->courseid)) { 721 // Check if course end date form field should be enabled by default. 722 // If a default date is provided to the form element, it is magically enabled by default in the 723 // MoodleQuickForm_date_time_selector class, otherwise it's disabled by default. 724 if (get_config('moodlecourse', 'courseenddateenabled')) { 725 // At this stage (this is called from definition_after_data) course data is already set as default. 726 // We can not overwrite what is in the database. 727 $mform->setDefault('enddate', $this->get_default_course_enddate($mform)); 728 } 729 } 730 731 return $elements; 732 } 733 734 /** 735 * Override if you need to perform some extra validation of the format options 736 * 737 * @param array $data array of ("fieldname"=>value) of submitted data 738 * @param array $files array of uploaded files "element_name"=>tmp_file_path 739 * @param array $errors errors already discovered in edit form validation 740 * @return array of "element_name"=>"error_description" if there are errors, 741 * or an empty array if everything is OK. 742 * Do not repeat errors from $errors param here 743 */ 744 public function edit_form_validation($data, $files, $errors) { 745 return array(); 746 } 747 748 /** 749 * Prepares values of course or section format options before storing them in DB 750 * 751 * If an option has invalid value it is not returned 752 * 753 * @param array $rawdata associative array of the proposed course/section format options 754 * @param int|null $sectionid null if it is course format option 755 * @return array array of options that have valid values 756 */ 757 protected function validate_format_options(array $rawdata, int $sectionid = null) : array { 758 if (!$sectionid) { 759 $allformatoptions = $this->course_format_options(true); 760 } else { 761 $allformatoptions = $this->section_format_options(true); 762 } 763 $data = array_intersect_key($rawdata, $allformatoptions); 764 foreach ($data as $key => $value) { 765 $option = $allformatoptions[$key] + ['type' => PARAM_RAW, 'element_type' => null, 'element_attributes' => [[]]]; 766 expand_value($data, $data, $option, $key); 767 if ($option['element_type'] === 'select' && !array_key_exists($data[$key], $option['element_attributes'][0])) { 768 // Value invalid for select element, skip. 769 unset($data[$key]); 770 } 771 } 772 return $data; 773 } 774 775 /** 776 * Validates format options for the course 777 * 778 * @param array $data data to insert/update 779 * @return array array of options that have valid values 780 */ 781 public function validate_course_format_options(array $data) : array { 782 return $this->validate_format_options($data); 783 } 784 785 /** 786 * Updates format options for a course or section 787 * 788 * If $data does not contain property with the option name, the option will not be updated 789 * 790 * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data 791 * @param null|int null if these are options for course or section id (course_sections.id) 792 * if these are options for section 793 * @return bool whether there were any changes to the options values 794 */ 795 protected function update_format_options($data, $sectionid = null) { 796 global $DB; 797 $data = $this->validate_format_options((array)$data, $sectionid); 798 if (!$sectionid) { 799 $allformatoptions = $this->course_format_options(); 800 $sectionid = 0; 801 } else { 802 $allformatoptions = $this->section_format_options(); 803 } 804 if (empty($allformatoptions)) { 805 // nothing to update anyway 806 return false; 807 } 808 $defaultoptions = array(); 809 $cached = array(); 810 foreach ($allformatoptions as $key => $option) { 811 $defaultoptions[$key] = null; 812 if (array_key_exists('default', $option)) { 813 $defaultoptions[$key] = $option['default']; 814 } 815 expand_value($defaultoptions, $defaultoptions, $option, $key); 816 $cached[$key] = ($sectionid === 0 || !empty($option['cache'])); 817 } 818 $records = $DB->get_records('course_format_options', 819 array('courseid' => $this->courseid, 820 'format' => $this->format, 821 'sectionid' => $sectionid 822 ), '', 'name,id,value'); 823 $changed = $needrebuild = false; 824 foreach ($defaultoptions as $key => $value) { 825 if (isset($records[$key])) { 826 if (array_key_exists($key, $data) && $records[$key]->value !== $data[$key]) { 827 $DB->set_field('course_format_options', 'value', 828 $data[$key], array('id' => $records[$key]->id)); 829 $changed = true; 830 $needrebuild = $needrebuild || $cached[$key]; 831 } 832 } else { 833 if (array_key_exists($key, $data) && $data[$key] !== $value) { 834 $newvalue = $data[$key]; 835 $changed = true; 836 $needrebuild = $needrebuild || $cached[$key]; 837 } else { 838 $newvalue = $value; 839 // we still insert entry in DB but there are no changes from user point of 840 // view and no need to call rebuild_course_cache() 841 } 842 $DB->insert_record('course_format_options', array( 843 'courseid' => $this->courseid, 844 'format' => $this->format, 845 'sectionid' => $sectionid, 846 'name' => $key, 847 'value' => $newvalue 848 )); 849 } 850 } 851 if ($needrebuild) { 852 rebuild_course_cache($this->courseid, true); 853 } 854 if ($changed) { 855 // reset internal caches 856 if (!$sectionid) { 857 $this->course = false; 858 } 859 unset($this->formatoptions[$sectionid]); 860 } 861 return $changed; 862 } 863 864 /** 865 * Updates format options for a course 866 * 867 * If $data does not contain property with the option name, the option will not be updated 868 * 869 * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data 870 * @param stdClass $oldcourse if this function is called from {@link update_course()} 871 * this object contains information about the course before update 872 * @return bool whether there were any changes to the options values 873 */ 874 public function update_course_format_options($data, $oldcourse = null) { 875 return $this->update_format_options($data); 876 } 877 878 /** 879 * Updates format options for a section 880 * 881 * Section id is expected in $data->id (or $data['id']) 882 * If $data does not contain property with the option name, the option will not be updated 883 * 884 * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data 885 * @return bool whether there were any changes to the options values 886 */ 887 public function update_section_format_options($data) { 888 $data = (array)$data; 889 return $this->update_format_options($data, $data['id']); 890 } 891 892 /** 893 * Return an instance of moodleform to edit a specified section 894 * 895 * Default implementation returns instance of editsection_form that automatically adds 896 * additional fields defined in {@link format_base::section_format_options()} 897 * 898 * Format plugins may extend editsection_form if they want to have custom edit section form. 899 * 900 * @param mixed $action the action attribute for the form. If empty defaults to auto detect the 901 * current url. If a moodle_url object then outputs params as hidden variables. 902 * @param array $customdata the array with custom data to be passed to the form 903 * /course/editsection.php passes section_info object in 'cs' field 904 * for filling availability fields 905 * @return moodleform 906 */ 907 public function editsection_form($action, $customdata = array()) { 908 global $CFG; 909 require_once($CFG->dirroot. '/course/editsection_form.php'); 910 $context = context_course::instance($this->courseid); 911 if (!array_key_exists('course', $customdata)) { 912 $customdata['course'] = $this->get_course(); 913 } 914 return new editsection_form($action, $customdata); 915 } 916 917 /** 918 * Allows course format to execute code on moodle_page::set_course() 919 * 920 * @param moodle_page $page instance of page calling set_course 921 */ 922 public function page_set_course(moodle_page $page) { 923 } 924 925 /** 926 * Allows course format to execute code on moodle_page::set_cm() 927 * 928 * Current module can be accessed as $page->cm (returns instance of cm_info) 929 * 930 * @param moodle_page $page instance of page calling set_cm 931 */ 932 public function page_set_cm(moodle_page $page) { 933 } 934 935 /** 936 * Course-specific information to be output on any course page (usually above navigation bar) 937 * 938 * Example of usage: 939 * define 940 * class format_FORMATNAME_XXX implements renderable {} 941 * 942 * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function: 943 * class format_FORMATNAME_renderer extends plugin_renderer_base { 944 * protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) { 945 * return html_writer::tag('div', 'This is my header/footer'); 946 * } 947 * } 948 * 949 * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from 950 * plugin renderer will be called 951 * 952 * @return null|renderable null for no output or object with data for plugin renderer 953 */ 954 public function course_header() { 955 return null; 956 } 957 958 /** 959 * Course-specific information to be output on any course page (usually in the beginning of 960 * standard footer) 961 * 962 * See {@link format_base::course_header()} for usage 963 * 964 * @return null|renderable null for no output or object with data for plugin renderer 965 */ 966 public function course_footer() { 967 return null; 968 } 969 970 /** 971 * Course-specific information to be output immediately above content on any course page 972 * 973 * See {@link format_base::course_header()} for usage 974 * 975 * @return null|renderable null for no output or object with data for plugin renderer 976 */ 977 public function course_content_header() { 978 return null; 979 } 980 981 /** 982 * Course-specific information to be output immediately below content on any course page 983 * 984 * See {@link format_base::course_header()} for usage 985 * 986 * @return null|renderable null for no output or object with data for plugin renderer 987 */ 988 public function course_content_footer() { 989 return null; 990 } 991 992 /** 993 * Returns instance of page renderer used by this plugin 994 * 995 * @param moodle_page $page 996 * @return renderer_base 997 */ 998 public function get_renderer(moodle_page $page) { 999 return $page->get_renderer('format_'. $this->get_format()); 1000 } 1001 1002 /** 1003 * Returns true if the specified section is current 1004 * 1005 * By default we analyze $course->marker 1006 * 1007 * @param int|stdClass|section_info $section 1008 * @return bool 1009 */ 1010 public function is_section_current($section) { 1011 if (is_object($section)) { 1012 $sectionnum = $section->section; 1013 } else { 1014 $sectionnum = $section; 1015 } 1016 return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum); 1017 } 1018 1019 /** 1020 * Allows to specify for modinfo that section is not available even when it is visible and conditionally available. 1021 * 1022 * Note: affected user can be retrieved as: $section->modinfo->userid 1023 * 1024 * Course format plugins can override the method to change the properties $available and $availableinfo that were 1025 * calculated by conditional availability. 1026 * To make section unavailable set: 1027 * $available = false; 1028 * To make unavailable section completely hidden set: 1029 * $availableinfo = ''; 1030 * To make unavailable section visible with availability message set: 1031 * $availableinfo = get_string('sectionhidden', 'format_xxx'); 1032 * 1033 * @param section_info $section 1034 * @param bool $available the 'available' propery of the section_info as it was evaluated by conditional availability. 1035 * Can be changed by the method but 'false' can not be overridden by 'true'. 1036 * @param string $availableinfo the 'availableinfo' propery of the section_info as it was evaluated by conditional availability. 1037 * Can be changed by the method 1038 */ 1039 public function section_get_available_hook(section_info $section, &$available, &$availableinfo) { 1040 } 1041 1042 /** 1043 * Whether this format allows to delete sections 1044 * 1045 * If format supports deleting sections it is also recommended to define language string 1046 * 'deletesection' inside the format. 1047 * 1048 * Do not call this function directly, instead use {@link course_can_delete_section()} 1049 * 1050 * @param int|stdClass|section_info $section 1051 * @return bool 1052 */ 1053 public function can_delete_section($section) { 1054 return false; 1055 } 1056 1057 /** 1058 * Deletes a section 1059 * 1060 * Do not call this function directly, instead call {@link course_delete_section()} 1061 * 1062 * @param int|stdClass|section_info $section 1063 * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it. 1064 * @return bool whether section was deleted 1065 */ 1066 public function delete_section($section, $forcedeleteifnotempty = false) { 1067 global $DB; 1068 if (!$this->uses_sections()) { 1069 // Not possible to delete section if sections are not used. 1070 return false; 1071 } 1072 if (!is_object($section)) { 1073 $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section), 1074 'id,section,sequence,summary'); 1075 } 1076 if (!$section || !$section->section) { 1077 // Not possible to delete 0-section. 1078 return false; 1079 } 1080 1081 if (!$forcedeleteifnotempty && (!empty($section->sequence) || !empty($section->summary))) { 1082 return false; 1083 } 1084 1085 $course = $this->get_course(); 1086 1087 // Remove the marker if it points to this section. 1088 if ($section->section == $course->marker) { 1089 course_set_marker($course->id, 0); 1090 } 1091 1092 $lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections} 1093 WHERE course = ?', array($course->id)); 1094 1095 // Find out if we need to descrease the 'numsections' property later. 1096 $courseformathasnumsections = array_key_exists('numsections', 1097 $this->get_format_options()); 1098 $decreasenumsections = $courseformathasnumsections && ($section->section <= $course->numsections); 1099 1100 // Move the section to the end. 1101 move_section_to($course, $section->section, $lastsection, true); 1102 1103 // Delete all modules from the section. 1104 foreach (preg_split('/,/', $section->sequence, -1, PREG_SPLIT_NO_EMPTY) as $cmid) { 1105 course_delete_module($cmid); 1106 } 1107 1108 // Delete section and it's format options. 1109 $DB->delete_records('course_format_options', array('sectionid' => $section->id)); 1110 $DB->delete_records('course_sections', array('id' => $section->id)); 1111 rebuild_course_cache($course->id, true); 1112 1113 // Delete section summary files. 1114 $context = \context_course::instance($course->id); 1115 $fs = get_file_storage(); 1116 $fs->delete_area_files($context->id, 'course', 'section', $section->id); 1117 1118 // Descrease 'numsections' if needed. 1119 if ($decreasenumsections) { 1120 $this->update_course_format_options(array('numsections' => $course->numsections - 1)); 1121 } 1122 1123 return true; 1124 } 1125 1126 /** 1127 * Prepares the templateable object to display section name 1128 * 1129 * @param \section_info|\stdClass $section 1130 * @param bool $linkifneeded 1131 * @param bool $editable 1132 * @param null|lang_string|string $edithint 1133 * @param null|lang_string|string $editlabel 1134 * @return \core\output\inplace_editable 1135 */ 1136 public function inplace_editable_render_section_name($section, $linkifneeded = true, 1137 $editable = null, $edithint = null, $editlabel = null) { 1138 global $USER, $CFG; 1139 require_once($CFG->dirroot.'/course/lib.php'); 1140 1141 if ($editable === null) { 1142 $editable = !empty($USER->editing) && has_capability('moodle/course:update', 1143 context_course::instance($section->course)); 1144 } 1145 1146 $displayvalue = $title = get_section_name($section->course, $section); 1147 if ($linkifneeded) { 1148 // Display link under the section name if the course format setting is to display one section per page. 1149 $url = course_get_url($section->course, $section->section, array('navigation' => true)); 1150 if ($url) { 1151 $displayvalue = html_writer::link($url, $title); 1152 } 1153 $itemtype = 'sectionname'; 1154 } else { 1155 // If $linkifneeded==false, we never display the link (this is used when rendering the section header). 1156 // Itemtype 'sectionnamenl' (nl=no link) will tell the callback that link should not be rendered - 1157 // there is no other way callback can know where we display the section name. 1158 $itemtype = 'sectionnamenl'; 1159 } 1160 if (empty($edithint)) { 1161 $edithint = new lang_string('editsectionname'); 1162 } 1163 if (empty($editlabel)) { 1164 $editlabel = new lang_string('newsectionname', '', $title); 1165 } 1166 1167 return new \core\output\inplace_editable('format_' . $this->format, $itemtype, $section->id, $editable, 1168 $displayvalue, $section->name, $edithint, $editlabel); 1169 } 1170 1171 /** 1172 * Updates the value in the database and modifies this object respectively. 1173 * 1174 * ALWAYS check user permissions before performing an update! Throw exceptions if permissions are not sufficient 1175 * or value is not legit. 1176 * 1177 * @param stdClass $section 1178 * @param string $itemtype 1179 * @param mixed $newvalue 1180 * @return \core\output\inplace_editable 1181 */ 1182 public function inplace_editable_update_section_name($section, $itemtype, $newvalue) { 1183 if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') { 1184 $context = context_course::instance($section->course); 1185 external_api::validate_context($context); 1186 require_capability('moodle/course:update', $context); 1187 1188 $newtitle = clean_param($newvalue, PARAM_TEXT); 1189 if (strval($section->name) !== strval($newtitle)) { 1190 course_update_section($section->course, $section, array('name' => $newtitle)); 1191 } 1192 return $this->inplace_editable_render_section_name($section, ($itemtype === 'sectionname'), true); 1193 } 1194 } 1195 1196 1197 /** 1198 * Returns the default end date value based on the start date. 1199 * 1200 * This is the default implementation for course formats, it is based on 1201 * moodlecourse/courseduration setting. Course formats like format_weeks for 1202 * example can overwrite this method and return a value based on their internal options. 1203 * 1204 * @param moodleform $mform 1205 * @param array $fieldnames The form - field names mapping. 1206 * @return int 1207 */ 1208 public function get_default_course_enddate($mform, $fieldnames = array()) { 1209 1210 if (empty($fieldnames)) { 1211 $fieldnames = array('startdate' => 'startdate'); 1212 } 1213 1214 $startdate = $this->get_form_start_date($mform, $fieldnames); 1215 $courseduration = intval(get_config('moodlecourse', 'courseduration')); 1216 if (!$courseduration) { 1217 // Default, it should be already set during upgrade though. 1218 $courseduration = YEARSECS; 1219 } 1220 1221 return $startdate + $courseduration; 1222 } 1223 1224 /** 1225 * Indicates whether the course format supports the creation of the Announcements forum. 1226 * 1227 * For course format plugin developers, please override this to return true if you want the Announcements forum 1228 * to be created upon course creation. 1229 * 1230 * @return bool 1231 */ 1232 public function supports_news() { 1233 // For backwards compatibility, check if default blocks include the news_items block. 1234 $defaultblocks = $this->get_default_blocks(); 1235 foreach ($defaultblocks as $blocks) { 1236 if (in_array('news_items', $blocks)) { 1237 return true; 1238 } 1239 } 1240 // Return false by default. 1241 return false; 1242 } 1243 1244 /** 1245 * Get the start date value from the course settings page form. 1246 * 1247 * @param moodleform $mform 1248 * @param array $fieldnames The form - field names mapping. 1249 * @return int 1250 */ 1251 protected function get_form_start_date($mform, $fieldnames) { 1252 $startdate = $mform->getElementValue($fieldnames['startdate']); 1253 return $mform->getElement($fieldnames['startdate'])->exportValue($startdate); 1254 } 1255 1256 /** 1257 * Returns whether this course format allows the activity to 1258 * have "triple visibility state" - visible always, hidden on course page but available, hidden. 1259 * 1260 * @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module) 1261 * @param stdClass|section_info $section section where this module is located or will be added to 1262 * @return bool 1263 */ 1264 public function allow_stealth_module_visibility($cm, $section) { 1265 return false; 1266 } 1267 1268 /** 1269 * Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide) 1270 * 1271 * Access to the course is already validated in the WS but the callback has to make sure 1272 * that particular action is allowed by checking capabilities 1273 * 1274 * Course formats should register 1275 * 1276 * @param stdClass|section_info $section 1277 * @param string $action 1278 * @param int $sr 1279 * @return null|array|stdClass any data for the Javascript post-processor (must be json-encodeable) 1280 */ 1281 public function section_action($section, $action, $sr) { 1282 global $PAGE; 1283 if (!$this->uses_sections() || !$section->section) { 1284 // No section actions are allowed if course format does not support sections. 1285 // No actions are allowed on the 0-section by default (overwrite in course format if needed). 1286 throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action)); 1287 } 1288 1289 $course = $this->get_course(); 1290 $coursecontext = context_course::instance($course->id); 1291 switch($action) { 1292 case 'hide': 1293 case 'show': 1294 require_capability('moodle/course:sectionvisibility', $coursecontext); 1295 $visible = ($action === 'hide') ? 0 : 1; 1296 course_update_section($course, $section, array('visible' => $visible)); 1297 break; 1298 default: 1299 throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action)); 1300 } 1301 1302 $modules = []; 1303 1304 $modinfo = get_fast_modinfo($course); 1305 $coursesections = $modinfo->sections; 1306 if (array_key_exists($section->section, $coursesections)) { 1307 $courserenderer = $PAGE->get_renderer('core', 'course'); 1308 $completioninfo = new completion_info($course); 1309 foreach ($coursesections[$section->section] as $cmid) { 1310 $cm = $modinfo->get_cm($cmid); 1311 $modules[] = $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sr); 1312 } 1313 } 1314 1315 return ['modules' => $modules]; 1316 } 1317 1318 /** 1319 * Return the plugin config settings for external functions, 1320 * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled. 1321 * 1322 * @return array the list of configs 1323 * @since Moodle 3.5 1324 */ 1325 public function get_config_for_external() { 1326 return array(); 1327 } 1328 } 1329 1330 /** 1331 * Pseudo course format used for the site main page 1332 * 1333 * @package core_course 1334 * @copyright 2012 Marina Glancy 1335 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1336 */ 1337 class format_site extends format_base { 1338 1339 /** 1340 * Returns the display name of the given section that the course prefers. 1341 * 1342 * @param int|stdClass $section Section object from database or just field section.section 1343 * @return Display name that the course format prefers, e.g. "Topic 2" 1344 */ 1345 function get_section_name($section) { 1346 return get_string('site'); 1347 } 1348 1349 /** 1350 * For this fake course referring to the whole site, the site homepage is always returned 1351 * regardless of arguments 1352 * 1353 * @param int|stdClass $section 1354 * @param array $options 1355 * @return null|moodle_url 1356 */ 1357 public function get_view_url($section, $options = array()) { 1358 return new moodle_url('/', array('redirect' => 0)); 1359 } 1360 1361 /** 1362 * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed 1363 * 1364 * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT 1365 * each of values is an array of block names (for left and right side columns) 1366 */ 1367 public function get_default_blocks() { 1368 return blocks_get_default_site_course_blocks(); 1369 } 1370 1371 /** 1372 * Definitions of the additional options that site uses 1373 * 1374 * @param bool $foreditform 1375 * @return array of options 1376 */ 1377 public function course_format_options($foreditform = false) { 1378 static $courseformatoptions = false; 1379 if ($courseformatoptions === false) { 1380 $courseformatoptions = array( 1381 'numsections' => array( 1382 'default' => 1, 1383 'type' => PARAM_INT, 1384 ), 1385 ); 1386 } 1387 return $courseformatoptions; 1388 } 1389 1390 /** 1391 * Returns whether this course format allows the activity to 1392 * have "triple visibility state" - visible always, hidden on course page but available, hidden. 1393 * 1394 * @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module) 1395 * @param stdClass|section_info $section section where this module is located or will be added to 1396 * @return bool 1397 */ 1398 public function allow_stealth_module_visibility($cm, $section) { 1399 return true; 1400 } 1401 } 1402 1403 /** 1404 * 'Converts' a value from what is stored in the database into what is used by edit forms. 1405 * 1406 * @param array $dest The destination array 1407 * @param array $source The source array 1408 * @param array $option The definition structure of the option. 1409 * @param string $optionname The name of the option, as provided in the definition. 1410 * @author Jason den Dulk 1411 */ 1412 function contract_value(array &$dest, array $source, array $option, string $optionname) : void { 1413 if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor. 1414 $name = substr($optionname, 0, -7); 1415 if (isset($source[$name])) { 1416 $dest[$optionname] = [ 1417 'text' => clean_param_if_not_null($source[$name], $option['type'] ?? PARAM_RAW), 1418 'format' => clean_param_if_not_null($source[$name . 'format'], PARAM_INT), 1419 ]; 1420 } 1421 } else { 1422 if (isset($source[$optionname])) { 1423 $dest[$optionname] = clean_param_if_not_null($source[$optionname], $option['type'] ?? PARAM_RAW); 1424 } 1425 } 1426 } 1427 1428 /** 1429 * Cleans the given param, unless it is null. 1430 * 1431 * @param mixed $param The variable we are cleaning. 1432 * @param string $type Expected format of param after cleaning. 1433 * @return mixed Null if $param is null, otherwise the cleaned value. 1434 * @throws coding_exception 1435 * @author Jason den Dulk 1436 */ 1437 function clean_param_if_not_null($param, string $type = PARAM_RAW) { 1438 if ($param === null) { 1439 return null; 1440 } else { 1441 return clean_param($param, $type); 1442 } 1443 } 1444 1445 /** 1446 * 'Converts' a value from what is used in edit forms into a value(s) to be stored in the database. 1447 * 1448 * @param array $dest The destination array 1449 * @param array $source The source array 1450 * @param array $option The definition structure of the option. 1451 * @param string $optionname The name of the option, as provided in the definition. 1452 * @author Jason den Dulk 1453 */ 1454 function expand_value(array &$dest, array $source, array $option, string $optionname) : void { 1455 if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor. 1456 $name = substr($optionname, 0, -7); 1457 if (is_string($source[$optionname])) { 1458 $dest[$name] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW); 1459 $dest[$name.'format'] = 1; 1460 } else { 1461 $dest[$name] = clean_param($source[$optionname]['text'], $option['type'] ?? PARAM_RAW); 1462 $dest[$name.'format'] = clean_param($source[$optionname]['format'], PARAM_INT); 1463 } 1464 unset($dest[$optionname]); 1465 } else { 1466 $dest[$optionname] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW); 1467 } 1468 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body