See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
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 Topics course format. 19 * 20 * @since Moodle 2.0 21 * @package format_topics 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 29 use core\output\inplace_editable; 30 31 /** 32 * Main class for the Topics course format. 33 * 34 * @package format_topics 35 * @copyright 2012 Marina Glancy 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class format_topics extends core_courseformat\base { 39 40 /** 41 * Returns true if this course format uses sections. 42 * 43 * @return bool 44 */ 45 public function uses_sections() { 46 return true; 47 } 48 49 public function uses_course_index() { 50 return true; 51 } 52 53 public function uses_indentation(): bool { 54 return (get_config('format_topics', 'indentation')) ? true : false; 55 } 56 57 /** 58 * Returns the display name of the given section that the course prefers. 59 * 60 * Use section name is specified by user. Otherwise use default ("Topic #"). 61 * 62 * @param int|stdClass $section Section object from database or just field section.section 63 * @return string Display name that the course format prefers, e.g. "Topic 2" 64 */ 65 public function get_section_name($section) { 66 $section = $this->get_section($section); 67 if ((string)$section->name !== '') { 68 return format_string($section->name, true, 69 ['context' => context_course::instance($this->courseid)]); 70 } else { 71 return $this->get_default_section_name($section); 72 } 73 } 74 75 /** 76 * Returns the default section name for the topics course format. 77 * 78 * If the section number is 0, it will use the string with key = section0name from the course format's lang file. 79 * If the section number is not 0, the base implementation of course_format::get_default_section_name which uses 80 * the string with the key = 'sectionname' from the course format's lang file + the section number will be used. 81 * 82 * @param stdClass $section Section object from database or just field course_sections section 83 * @return string The default value for the section name. 84 */ 85 public function get_default_section_name($section) { 86 if ($section->section == 0) { 87 // Return the general section. 88 return get_string('section0name', 'format_topics'); 89 } else { 90 // Use course_format::get_default_section_name implementation which 91 // will display the section name in "Topic n" format. 92 return parent::get_default_section_name($section); 93 } 94 } 95 96 /** 97 * Generate the title for this section page. 98 * 99 * @return string the page title 100 */ 101 public function page_title(): string { 102 return get_string('topicoutline'); 103 } 104 105 /** 106 * The URL to use for the specified course (with section). 107 * 108 * @param int|stdClass $section Section object from database or just field course_sections.section 109 * if omitted the course view page is returned 110 * @param array $options options for view URL. At the moment core uses: 111 * 'navigation' (bool) if true and section has no separate page, the function returns null 112 * 'sr' (int) used by multipage formats to specify to which section to return 113 * @return null|moodle_url 114 */ 115 public function get_view_url($section, $options = []) { 116 global $CFG; 117 $course = $this->get_course(); 118 $url = new moodle_url('/course/view.php', ['id' => $course->id]); 119 120 $sr = null; 121 if (array_key_exists('sr', $options)) { 122 $sr = $options['sr']; 123 } 124 if (is_object($section)) { 125 $sectionno = $section->section; 126 } else { 127 $sectionno = $section; 128 } 129 if ($sectionno !== null) { 130 if ($sr !== null) { 131 if ($sr) { 132 $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE; 133 $sectionno = $sr; 134 } else { 135 $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE; 136 } 137 } else { 138 $usercoursedisplay = $course->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE; 139 } 140 if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) { 141 $url->param('section', $sectionno); 142 } else { 143 if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) { 144 return null; 145 } 146 $url->set_anchor('section-'.$sectionno); 147 } 148 } 149 return $url; 150 } 151 152 /** 153 * Returns the information about the ajax support in the given source format. 154 * 155 * The returned object's property (boolean)capable indicates that 156 * the course format supports Moodle course ajax features. 157 * 158 * @return stdClass 159 */ 160 public function supports_ajax() { 161 $ajaxsupport = new stdClass(); 162 $ajaxsupport->capable = true; 163 return $ajaxsupport; 164 } 165 166 public function supports_components() { 167 return true; 168 } 169 170 /** 171 * Loads all of the course sections into the navigation. 172 * 173 * @param global_navigation $navigation 174 * @param navigation_node $node The course node within the navigation 175 * @return void 176 */ 177 public function extend_course_navigation($navigation, navigation_node $node) { 178 global $PAGE; 179 // If section is specified in course/view.php, make sure it is expanded in navigation. 180 if ($navigation->includesectionnum === false) { 181 $selectedsection = optional_param('section', null, PARAM_INT); 182 if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') && 183 $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) { 184 $navigation->includesectionnum = $selectedsection; 185 } 186 } 187 188 // Check if there are callbacks to extend course navigation. 189 parent::extend_course_navigation($navigation, $node); 190 191 // We want to remove the general section if it is empty. 192 $modinfo = get_fast_modinfo($this->get_course()); 193 $sections = $modinfo->get_sections(); 194 if (!isset($sections[0])) { 195 // The general section is empty to find the navigation node for it we need to get its ID. 196 $section = $modinfo->get_section_info(0); 197 $generalsection = $node->get($section->id, navigation_node::TYPE_SECTION); 198 if ($generalsection) { 199 // We found the node - now remove it. 200 $generalsection->remove(); 201 } 202 } 203 } 204 205 /** 206 * Custom action after section has been moved in AJAX mode. 207 * 208 * Used in course/rest.php 209 * 210 * @return array This will be passed in ajax respose 211 */ 212 public function ajax_section_move() { 213 global $PAGE; 214 $titles = []; 215 $course = $this->get_course(); 216 $modinfo = get_fast_modinfo($course); 217 $renderer = $this->get_renderer($PAGE); 218 if ($renderer && ($sections = $modinfo->get_section_info_all())) { 219 foreach ($sections as $number => $section) { 220 $titles[$number] = $renderer->section_title($section, $course); 221 } 222 } 223 return ['sectiontitles' => $titles, 'action' => 'move']; 224 } 225 226 /** 227 * Returns the list of blocks to be automatically added for the newly created course. 228 * 229 * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT 230 * each of values is an array of block names (for left and right side columns) 231 */ 232 public function get_default_blocks() { 233 return [ 234 BLOCK_POS_LEFT => [], 235 BLOCK_POS_RIGHT => [], 236 ]; 237 } 238 239 /** 240 * Definitions of the additional options that this course format uses for course. 241 * 242 * Topics format uses the following options: 243 * - coursedisplay 244 * - hiddensections 245 * 246 * @param bool $foreditform 247 * @return array of options 248 */ 249 public function course_format_options($foreditform = false) { 250 static $courseformatoptions = false; 251 if ($courseformatoptions === false) { 252 $courseconfig = get_config('moodlecourse'); 253 $courseformatoptions = [ 254 'hiddensections' => [ 255 'default' => $courseconfig->hiddensections, 256 'type' => PARAM_INT, 257 ], 258 'coursedisplay' => [ 259 'default' => $courseconfig->coursedisplay, 260 'type' => PARAM_INT, 261 ], 262 ]; 263 } 264 if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) { 265 $courseformatoptionsedit = [ 266 'hiddensections' => [ 267 'label' => new lang_string('hiddensections'), 268 'help' => 'hiddensections', 269 'help_component' => 'moodle', 270 'element_type' => 'select', 271 'element_attributes' => [ 272 [ 273 0 => new lang_string('hiddensectionscollapsed'), 274 1 => new lang_string('hiddensectionsinvisible') 275 ], 276 ], 277 ], 278 'coursedisplay' => [ 279 'label' => new lang_string('coursedisplay'), 280 'element_type' => 'select', 281 'element_attributes' => [ 282 [ 283 COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'), 284 COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi'), 285 ], 286 ], 287 'help' => 'coursedisplay', 288 'help_component' => 'moodle', 289 ], 290 ]; 291 $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit); 292 } 293 return $courseformatoptions; 294 } 295 296 /** 297 * Adds format options elements to the course/section edit form. 298 * 299 * This function is called from {@link course_edit_form::definition_after_data()}. 300 * 301 * @param MoodleQuickForm $mform form the elements are added to. 302 * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form. 303 * @return array array of references to the added form elements. 304 */ 305 public function create_edit_form_elements(&$mform, $forsection = false) { 306 global $COURSE; 307 $elements = parent::create_edit_form_elements($mform, $forsection); 308 309 if (!$forsection && (empty($COURSE->id) || $COURSE->id == SITEID)) { 310 // Add "numsections" element to the create course form - it will force new course to be prepopulated 311 // with empty sections. 312 // The "Number of sections" option is no longer available when editing course, instead teachers should 313 // delete and add sections when needed. 314 $courseconfig = get_config('moodlecourse'); 315 $max = (int)$courseconfig->maxsections; 316 $element = $mform->addElement('select', 'numsections', get_string('numberweeks'), range(0, $max ?: 52)); 317 $mform->setType('numsections', PARAM_INT); 318 if (is_null($mform->getElementValue('numsections'))) { 319 $mform->setDefault('numsections', $courseconfig->numsections); 320 } 321 array_unshift($elements, $element); 322 } 323 324 return $elements; 325 } 326 327 /** 328 * Updates format options for a course. 329 * 330 * In case if course format was changed to 'topics', we try to copy options 331 * 'coursedisplay' and 'hiddensections' from the previous format. 332 * 333 * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data 334 * @param stdClass $oldcourse if this function is called from {@link update_course()} 335 * this object contains information about the course before update 336 * @return bool whether there were any changes to the options values 337 */ 338 public function update_course_format_options($data, $oldcourse = null) { 339 $data = (array)$data; 340 if ($oldcourse !== null) { 341 $oldcourse = (array)$oldcourse; 342 $options = $this->course_format_options(); 343 foreach ($options as $key => $unused) { 344 if (!array_key_exists($key, $data)) { 345 if (array_key_exists($key, $oldcourse)) { 346 $data[$key] = $oldcourse[$key]; 347 } 348 } 349 } 350 } 351 return $this->update_format_options($data); 352 } 353 354 /** 355 * Whether this format allows to delete sections. 356 * 357 * Do not call this function directly, instead use {@link course_can_delete_section()} 358 * 359 * @param int|stdClass|section_info $section 360 * @return bool 361 */ 362 public function can_delete_section($section) { 363 return true; 364 } 365 366 /** 367 * Prepares the templateable object to display section name. 368 * 369 * @param \section_info|\stdClass $section 370 * @param bool $linkifneeded 371 * @param bool $editable 372 * @param null|lang_string|string $edithint 373 * @param null|lang_string|string $editlabel 374 * @return inplace_editable 375 */ 376 public function inplace_editable_render_section_name($section, $linkifneeded = true, 377 $editable = null, $edithint = null, $editlabel = null) { 378 if (empty($edithint)) { 379 $edithint = new lang_string('editsectionname', 'format_topics'); 380 } 381 if (empty($editlabel)) { 382 $title = get_section_name($section->course, $section); 383 $editlabel = new lang_string('newsectionname', 'format_topics', $title); 384 } 385 return parent::inplace_editable_render_section_name($section, $linkifneeded, $editable, $edithint, $editlabel); 386 } 387 388 /** 389 * Indicates whether the course format supports the creation of a news forum. 390 * 391 * @return bool 392 */ 393 public function supports_news() { 394 return true; 395 } 396 397 /** 398 * Returns whether this course format allows the activity to 399 * have "triple visibility state" - visible always, hidden on course page but available, hidden. 400 * 401 * @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module) 402 * @param stdClass|section_info $section section where this module is located or will be added to 403 * @return bool 404 */ 405 public function allow_stealth_module_visibility($cm, $section) { 406 // Allow the third visibility state inside visible sections or in section 0. 407 return !$section->section || $section->visible; 408 } 409 410 /** 411 * Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide). 412 * 413 * Access to the course is already validated in the WS but the callback has to make sure 414 * that particular action is allowed by checking capabilities 415 * 416 * Course formats should register. 417 * 418 * @param section_info|stdClass $section 419 * @param string $action 420 * @param int $sr 421 * @return null|array any data for the Javascript post-processor (must be json-encodeable) 422 */ 423 public function section_action($section, $action, $sr) { 424 global $PAGE; 425 426 if ($section->section && ($action === 'setmarker' || $action === 'removemarker')) { 427 // Format 'topics' allows to set and remove markers in addition to common section actions. 428 require_capability('moodle/course:setcurrentsection', context_course::instance($this->courseid)); 429 course_set_marker($this->courseid, ($action === 'setmarker') ? $section->section : 0); 430 return null; 431 } 432 433 // For show/hide actions call the parent method and return the new content for .section_availability element. 434 $rv = parent::section_action($section, $action, $sr); 435 $renderer = $PAGE->get_renderer('format_topics'); 436 437 if (!($section instanceof section_info)) { 438 $modinfo = course_modinfo::instance($this->courseid); 439 $section = $modinfo->get_section_info($section->section); 440 } 441 $elementclass = $this->get_output_classname('content\\section\\availability'); 442 $availability = new $elementclass($this, $section); 443 444 $rv['section_availability'] = $renderer->render($availability); 445 return $rv; 446 } 447 448 /** 449 * Return the plugin configs for external functions. 450 * 451 * @return array the list of configuration settings 452 * @since Moodle 3.5 453 */ 454 public function get_config_for_external() { 455 // Return everything (nothing to hide). 456 $formatoptions = $this->get_format_options(); 457 $formatoptions['indentation'] = get_config('format_topics', 'indentation'); 458 return $formatoptions; 459 } 460 } 461 462 /** 463 * Implements callback inplace_editable() allowing to edit values in-place. 464 * 465 * @param string $itemtype 466 * @param int $itemid 467 * @param mixed $newvalue 468 * @return inplace_editable 469 */ 470 function format_topics_inplace_editable($itemtype, $itemid, $newvalue) { 471 global $DB, $CFG; 472 require_once($CFG->dirroot . '/course/lib.php'); 473 if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') { 474 $section = $DB->get_record_sql( 475 'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?', 476 [$itemid, 'topics'], MUST_EXIST); 477 return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue); 478 } 479 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body