See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 * This file contains main class for the course format Weeks 19 * 20 * @since Moodle 2.0 21 * @package format_weeks 22 * @copyright 2009 Sam Hemelryk 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 require_once($CFG->dirroot. '/course/format/lib.php'); 28 require_once($CFG->dirroot. '/course/lib.php'); 29 30 /** 31 * Main class for the Weeks course format 32 * 33 * @package format_weeks 34 * @copyright 2012 Marina Glancy 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class format_weeks extends format_base { 38 39 /** 40 * Returns true if this course format uses sections 41 * 42 * @return bool 43 */ 44 public function uses_sections() { 45 return true; 46 } 47 48 /** 49 * Returns the display name of the given section that the course prefers. 50 * 51 * @param int|stdClass $section Section object from database or just field section.section 52 * @return string Display name that the course format prefers, e.g. "Topic 2" 53 */ 54 public function get_section_name($section) { 55 $section = $this->get_section($section); 56 if ((string)$section->name !== '') { 57 // Return the name the user set. 58 return format_string($section->name, true, array('context' => context_course::instance($this->courseid))); 59 } else { 60 return $this->get_default_section_name($section); 61 } 62 } 63 64 /** 65 * Returns the default section name for the weekly course format. 66 * 67 * If the section number is 0, it will use the string with key = section0name from the course format's lang file. 68 * Otherwise, the default format of "[start date] - [end date]" will be returned. 69 * 70 * @param stdClass $section Section object from database or just field course_sections section 71 * @return string The default value for the section name. 72 */ 73 public function get_default_section_name($section) { 74 if ($section->section == 0) { 75 // Return the general section. 76 return get_string('section0name', 'format_weeks'); 77 } else { 78 $dates = $this->get_section_dates($section); 79 80 // We subtract 24 hours for display purposes. 81 $dates->end = ($dates->end - 86400); 82 83 $dateformat = get_string('strftimedateshort'); 84 $weekday = userdate($dates->start, $dateformat); 85 $endweekday = userdate($dates->end, $dateformat); 86 return $weekday.' - '.$endweekday; 87 } 88 } 89 90 /** 91 * The URL to use for the specified course (with section) 92 * 93 * @param int|stdClass $section Section object from database or just field course_sections.section 94 * if omitted the course view page is returned 95 * @param array $options options for view URL. At the moment core uses: 96 * 'navigation' (bool) if true and section has no separate page, the function returns null 97 * 'sr' (int) used by multipage formats to specify to which section to return 98 * @return null|moodle_url 99 */ 100 public function get_view_url($section, $options = array()) { 101 global $CFG; 102 $course = $this->get_course(); 103 $url = new moodle_url('/course/view.php', array('id' => $course->id)); 104 105 $sr = null; 106 if (array_key_exists('sr', $options)) { 107 $sr = $options['sr']; 108 } 109 if (is_object($section)) { 110 $sectionno = $section->section; 111 } else { 112 $sectionno = $section; 113 } 114 if ($sectionno !== null) { 115 if ($sr !== null) { 116 if ($sr) { 117 $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE; 118 $sectionno = $sr; 119 } else { 120 $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE; 121 } 122 } else { 123 $usercoursedisplay = $course->coursedisplay; 124 } 125 if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) { 126 $url->param('section', $sectionno); 127 } else { 128 if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) { 129 return null; 130 } 131 $url->set_anchor('section-'.$sectionno); 132 } 133 } 134 return $url; 135 } 136 137 /** 138 * Returns the information about the ajax support in the given source format 139 * 140 * The returned object's property (boolean)capable indicates that 141 * the course format supports Moodle course ajax features. 142 * 143 * @return stdClass 144 */ 145 public function supports_ajax() { 146 $ajaxsupport = new stdClass(); 147 $ajaxsupport->capable = true; 148 return $ajaxsupport; 149 } 150 151 /** 152 * Loads all of the course sections into the navigation 153 * 154 * @param global_navigation $navigation 155 * @param navigation_node $node The course node within the navigation 156 */ 157 public function extend_course_navigation($navigation, navigation_node $node) { 158 global $PAGE; 159 // if section is specified in course/view.php, make sure it is expanded in navigation 160 if ($navigation->includesectionnum === false) { 161 $selectedsection = optional_param('section', null, PARAM_INT); 162 if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') && 163 $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) { 164 $navigation->includesectionnum = $selectedsection; 165 } 166 } 167 parent::extend_course_navigation($navigation, $node); 168 169 // We want to remove the general section if it is empty. 170 $modinfo = get_fast_modinfo($this->get_course()); 171 $sections = $modinfo->get_sections(); 172 if (!isset($sections[0])) { 173 // The general section is empty to find the navigation node for it we need to get its ID. 174 $section = $modinfo->get_section_info(0); 175 $generalsection = $node->get($section->id, navigation_node::TYPE_SECTION); 176 if ($generalsection) { 177 // We found the node - now remove it. 178 $generalsection->remove(); 179 } 180 } 181 } 182 183 /** 184 * Custom action after section has been moved in AJAX mode 185 * 186 * Used in course/rest.php 187 * 188 * @return array This will be passed in ajax respose 189 */ 190 function ajax_section_move() { 191 global $PAGE; 192 $titles = array(); 193 $current = -1; 194 $course = $this->get_course(); 195 $modinfo = get_fast_modinfo($course); 196 $renderer = $this->get_renderer($PAGE); 197 if ($renderer && ($sections = $modinfo->get_section_info_all())) { 198 foreach ($sections as $number => $section) { 199 $titles[$number] = $renderer->section_title($section, $course); 200 if ($this->is_section_current($section)) { 201 $current = $number; 202 } 203 } 204 } 205 return array('sectiontitles' => $titles, 'current' => $current, 'action' => 'move'); 206 } 207 208 /** 209 * Returns the list of blocks to be automatically added for the newly created course 210 * 211 * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT 212 * each of values is an array of block names (for left and right side columns) 213 */ 214 public function get_default_blocks() { 215 return array( 216 BLOCK_POS_LEFT => array(), 217 BLOCK_POS_RIGHT => array() 218 ); 219 } 220 221 /** 222 * Definitions of the additional options that this course format uses for course 223 * 224 * Weeks format uses the following options: 225 * - coursedisplay 226 * - hiddensections 227 * - automaticenddate 228 * 229 * @param bool $foreditform 230 * @return array of options 231 */ 232 public function course_format_options($foreditform = false) { 233 static $courseformatoptions = false; 234 if ($courseformatoptions === false) { 235 $courseconfig = get_config('moodlecourse'); 236 $courseformatoptions = array( 237 'hiddensections' => array( 238 'default' => $courseconfig->hiddensections, 239 'type' => PARAM_INT, 240 ), 241 'coursedisplay' => array( 242 'default' => $courseconfig->coursedisplay, 243 'type' => PARAM_INT, 244 ), 245 'automaticenddate' => array( 246 'default' => 1, 247 'type' => PARAM_BOOL, 248 ), 249 ); 250 } 251 if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) { 252 $courseformatoptionsedit = array( 253 'hiddensections' => array( 254 'label' => new lang_string('hiddensections'), 255 'help' => 'hiddensections', 256 'help_component' => 'moodle', 257 'element_type' => 'select', 258 'element_attributes' => array( 259 array( 260 0 => new lang_string('hiddensectionscollapsed'), 261 1 => new lang_string('hiddensectionsinvisible') 262 ) 263 ), 264 ), 265 'coursedisplay' => array( 266 'label' => new lang_string('coursedisplay'), 267 'element_type' => 'select', 268 'element_attributes' => array( 269 array( 270 COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'), 271 COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi') 272 ) 273 ), 274 'help' => 'coursedisplay', 275 'help_component' => 'moodle', 276 ), 277 'automaticenddate' => array( 278 'label' => new lang_string('automaticenddate', 'format_weeks'), 279 'help' => 'automaticenddate', 280 'help_component' => 'format_weeks', 281 'element_type' => 'advcheckbox', 282 ) 283 ); 284 $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit); 285 } 286 return $courseformatoptions; 287 } 288 289 /** 290 * Adds format options elements to the course/section edit form. 291 * 292 * This function is called from {@link course_edit_form::definition_after_data()}. 293 * 294 * @param MoodleQuickForm $mform form the elements are added to. 295 * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form. 296 * @return array array of references to the added form elements. 297 */ 298 public function create_edit_form_elements(&$mform, $forsection = false) { 299 global $COURSE; 300 $elements = parent::create_edit_form_elements($mform, $forsection); 301 302 if (!$forsection && (empty($COURSE->id) || $COURSE->id == SITEID)) { 303 // Add "numsections" element to the create course form - it will force new course to be prepopulated 304 // with empty sections. 305 // The "Number of sections" option is no longer available when editing course, instead teachers should 306 // delete and add sections when needed. 307 $courseconfig = get_config('moodlecourse'); 308 $max = (int)$courseconfig->maxsections; 309 $element = $mform->addElement('select', 'numsections', get_string('numberweeks'), range(0, $max ?: 52)); 310 $mform->setType('numsections', PARAM_INT); 311 if (is_null($mform->getElementValue('numsections'))) { 312 $mform->setDefault('numsections', $courseconfig->numsections); 313 } 314 array_unshift($elements, $element); 315 } 316 317 // Re-order things. 318 $mform->insertElementBefore($mform->removeElement('automaticenddate', false), 'idnumber'); 319 $mform->disabledIf('enddate', 'automaticenddate', 'checked'); 320 foreach ($elements as $key => $element) { 321 if ($element->getName() == 'automaticenddate') { 322 unset($elements[$key]); 323 } 324 } 325 326 return $elements; 327 } 328 329 /** 330 * Updates format options for a course 331 * 332 * In case if course format was changed to 'weeks', we try to copy options 333 * 'coursedisplay', 'numsections' and 'hiddensections' from the previous format. 334 * If previous course format did not have 'numsections' option, we populate it with the 335 * current number of sections 336 * 337 * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data 338 * @param stdClass $oldcourse if this function is called from {@link update_course()} 339 * this object contains information about the course before update 340 * @return bool whether there were any changes to the options values 341 */ 342 public function update_course_format_options($data, $oldcourse = null) { 343 global $DB; 344 $data = (array)$data; 345 if ($oldcourse !== null) { 346 $oldcourse = (array)$oldcourse; 347 $options = $this->course_format_options(); 348 foreach ($options as $key => $unused) { 349 if (!array_key_exists($key, $data)) { 350 if (array_key_exists($key, $oldcourse)) { 351 $data[$key] = $oldcourse[$key]; 352 } 353 } 354 } 355 } 356 return $this->update_format_options($data); 357 } 358 359 /** 360 * Return the start and end date of the passed section 361 * 362 * @param int|stdClass|section_info $section section to get the dates for 363 * @param int $startdate Force course start date, useful when the course is not yet created 364 * @return stdClass property start for startdate, property end for enddate 365 */ 366 public function get_section_dates($section, $startdate = false) { 367 global $USER; 368 369 if ($startdate === false) { 370 $course = $this->get_course(); 371 $userdates = course_get_course_dates_for_user_id($course, $USER->id); 372 $startdate = $userdates['start']; 373 } 374 375 if (is_object($section)) { 376 $sectionnum = $section->section; 377 } else { 378 $sectionnum = $section; 379 } 380 $oneweekseconds = 604800; 381 // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight 382 // savings and the date changes. 383 $startdate = $startdate + 7200; 384 385 $dates = new stdClass(); 386 $dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1)); 387 $dates->end = $dates->start + $oneweekseconds; 388 389 return $dates; 390 } 391 392 /** 393 * Returns true if the specified week is current 394 * 395 * @param int|stdClass|section_info $section 396 * @return bool 397 */ 398 public function is_section_current($section) { 399 if (is_object($section)) { 400 $sectionnum = $section->section; 401 } else { 402 $sectionnum = $section; 403 } 404 if ($sectionnum < 1) { 405 return false; 406 } 407 $timenow = time(); 408 $dates = $this->get_section_dates($section); 409 return (($timenow >= $dates->start) && ($timenow < $dates->end)); 410 } 411 412 /** 413 * Whether this format allows to delete sections 414 * 415 * Do not call this function directly, instead use {@link course_can_delete_section()} 416 * 417 * @param int|stdClass|section_info $section 418 * @return bool 419 */ 420 public function can_delete_section($section) { 421 return true; 422 } 423 424 /** 425 * Prepares the templateable object to display section name 426 * 427 * @param \section_info|\stdClass $section 428 * @param bool $linkifneeded 429 * @param bool $editable 430 * @param null|lang_string|string $edithint 431 * @param null|lang_string|string $editlabel 432 * @return \core\output\inplace_editable 433 */ 434 public function inplace_editable_render_section_name($section, $linkifneeded = true, 435 $editable = null, $edithint = null, $editlabel = null) { 436 if (empty($edithint)) { 437 $edithint = new lang_string('editsectionname', 'format_weeks'); 438 } 439 if (empty($editlabel)) { 440 $title = get_section_name($section->course, $section); 441 $editlabel = new lang_string('newsectionname', 'format_weeks', $title); 442 } 443 return parent::inplace_editable_render_section_name($section, $linkifneeded, $editable, $edithint, $editlabel); 444 } 445 446 /** 447 * Returns the default end date for weeks course format. 448 * 449 * @param moodleform $mform 450 * @param array $fieldnames The form - field names mapping. 451 * @return int 452 */ 453 public function get_default_course_enddate($mform, $fieldnames = array()) { 454 455 if (empty($fieldnames['startdate'])) { 456 $fieldnames['startdate'] = 'startdate'; 457 } 458 459 if (empty($fieldnames['numsections'])) { 460 $fieldnames['numsections'] = 'numsections'; 461 } 462 463 $startdate = $this->get_form_start_date($mform, $fieldnames); 464 if ($mform->elementExists($fieldnames['numsections'])) { 465 $numsections = $mform->getElementValue($fieldnames['numsections']); 466 $numsections = $mform->getElement($fieldnames['numsections'])->exportValue($numsections); 467 } else if ($this->get_courseid()) { 468 // For existing courses get the number of sections. 469 $numsections = $this->get_last_section_number(); 470 } else { 471 // Fallback to the default value for new courses. 472 $numsections = get_config('moodlecourse', $fieldnames['numsections']); 473 } 474 475 // Final week's last day. 476 $dates = $this->get_section_dates(intval($numsections), $startdate); 477 return $dates->end; 478 } 479 480 /** 481 * Indicates whether the course format supports the creation of a news forum. 482 * 483 * @return bool 484 */ 485 public function supports_news() { 486 return true; 487 } 488 489 /** 490 * Returns whether this course format allows the activity to 491 * have "triple visibility state" - visible always, hidden on course page but available, hidden. 492 * 493 * @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module) 494 * @param stdClass|section_info $section section where this module is located or will be added to 495 * @return bool 496 */ 497 public function allow_stealth_module_visibility($cm, $section) { 498 // Allow the third visibility state inside visible sections or in section 0. 499 return !$section->section || $section->visible; 500 } 501 502 public function section_action($section, $action, $sr) { 503 global $PAGE; 504 505 // Call the parent method and return the new content for .section_availability element. 506 $rv = parent::section_action($section, $action, $sr); 507 $renderer = $PAGE->get_renderer('format_weeks'); 508 $rv['section_availability'] = $renderer->section_availability($this->get_section($section)); 509 return $rv; 510 } 511 512 /** 513 * Updates the end date for a course in weeks format if option automaticenddate is set. 514 * 515 * This method is called from event observers and it can not use any modinfo or format caches because 516 * events are triggered before the caches are reset. 517 * 518 * @param int $courseid 519 */ 520 public static function update_end_date($courseid) { 521 global $DB, $COURSE; 522 523 // Use one DB query to retrieve necessary fields in course, value for automaticenddate and number of the last 524 // section. This query will also validate that the course is indeed in 'weeks' format. 525 $insql = "SELECT c.id, c.format, c.startdate, c.enddate, MAX(s.section) AS lastsection 526 FROM {course} c 527 JOIN {course_sections} s 528 ON s.course = c.id 529 WHERE c.format = :format 530 AND c.id = :courseid 531 GROUP BY c.id, c.format, c.startdate, c.enddate"; 532 $sql = "SELECT co.id, co.format, co.startdate, co.enddate, co.lastsection, fo.value AS automaticenddate 533 FROM ($insql) co 534 LEFT JOIN {course_format_options} fo 535 ON fo.courseid = co.id 536 AND fo.format = co.format 537 AND fo.name = :optionname 538 AND fo.sectionid = 0"; 539 $course = $DB->get_record_sql($sql, 540 ['optionname' => 'automaticenddate', 'format' => 'weeks', 'courseid' => $courseid]); 541 542 if (!$course) { 543 // Looks like it is a course in a different format, nothing to do here. 544 return; 545 } 546 547 // Create an instance of this class and mock the course object. 548 $format = new format_weeks('weeks', $courseid); 549 $format->course = $course; 550 551 // If automaticenddate is not specified take the default value. 552 if (!isset($course->automaticenddate)) { 553 $defaults = $format->course_format_options(); 554 $course->automaticenddate = $defaults['automaticenddate']['default']; 555 } 556 557 // Check that the course format for setting an automatic date is set. 558 if (!empty($course->automaticenddate)) { 559 // Get the final week's last day. 560 $dates = $format->get_section_dates((int)$course->lastsection); 561 562 // Set the course end date. 563 if ($course->enddate != $dates->end) { 564 $DB->set_field('course', 'enddate', $dates->end, array('id' => $course->id)); 565 if (isset($COURSE->id) && $COURSE->id == $courseid) { 566 $COURSE->enddate = $dates->end; 567 } 568 } 569 } 570 } 571 572 /** 573 * Return the plugin configs for external functions. 574 * 575 * @return array the list of configuration settings 576 * @since Moodle 3.5 577 */ 578 public function get_config_for_external() { 579 // Return everything (nothing to hide). 580 return $this->get_format_options(); 581 } 582 } 583 584 /** 585 * Implements callback inplace_editable() allowing to edit values in-place 586 * 587 * @param string $itemtype 588 * @param int $itemid 589 * @param mixed $newvalue 590 * @return \core\output\inplace_editable 591 */ 592 function format_weeks_inplace_editable($itemtype, $itemid, $newvalue) { 593 global $DB, $CFG; 594 require_once($CFG->dirroot . '/course/lib.php'); 595 if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') { 596 $section = $DB->get_record_sql( 597 'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?', 598 array($itemid, 'weeks'), MUST_EXIST); 599 return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue); 600 } 601 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body