See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 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 the course format singleactivity 19 * 20 * @package format_singleactivity 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 require_once($CFG->dirroot. '/course/format/lib.php'); 27 28 /** 29 * Main class for the singleactivity course format 30 * 31 * @package format_singleactivity 32 * @copyright 2012 Marina Glancy 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class format_singleactivity extends core_courseformat\base { 36 /** @var cm_info the current activity. Use get_activity() to retrieve it. */ 37 private $activity = false; 38 39 /** @var int The category ID guessed from the form data. */ 40 private $categoryid = false; 41 42 /** 43 * The URL to use for the specified course 44 * 45 * @param int|stdClass $section Section object from database or just field course_sections.section 46 * if null the course view page is returned 47 * @param array $options options for view URL. At the moment core uses: 48 * 'navigation' (bool) if true and section has no separate page, the function returns null 49 * 'sr' (int) used by multipage formats to specify to which section to return 50 * @return null|moodle_url 51 */ 52 public function get_view_url($section, $options = array()) { 53 $sectionnum = $section; 54 if (is_object($sectionnum)) { 55 $sectionnum = $section->section; 56 } 57 if ($sectionnum == 1) { 58 return new moodle_url('/course/view.php', array('id' => $this->courseid, 'section' => 1)); 59 } 60 if (!empty($options['navigation']) && $section !== null) { 61 return null; 62 } 63 return new moodle_url('/course/view.php', array('id' => $this->courseid)); 64 } 65 66 /** 67 * Loads all of the course sections into the navigation 68 * 69 * @param global_navigation $navigation 70 * @param navigation_node $node The course node within the navigation 71 */ 72 public function extend_course_navigation($navigation, navigation_node $node) { 73 // Display orphaned activities for the users who can see them. 74 $context = context_course::instance($this->courseid); 75 if (has_capability('moodle/course:viewhiddensections', $context)) { 76 $modinfo = get_fast_modinfo($this->courseid); 77 if (!empty($modinfo->sections[1])) { 78 $section1 = $modinfo->get_section_info(1); 79 // Show orphaned activities. 80 $orphanednode = $node->add(get_string('orphaned', 'format_singleactivity'), 81 $this->get_view_url(1), navigation_node::TYPE_SECTION, null, $section1->id); 82 $orphanednode->nodetype = navigation_node::NODETYPE_BRANCH; 83 $orphanednode->add_class('orphaned'); 84 foreach ($modinfo->sections[1] as $cmid) { 85 if (has_capability('moodle/course:viewhiddenactivities', context_module::instance($cmid))) { 86 $this->navigation_add_activity($orphanednode, $modinfo->cms[$cmid]); 87 } 88 } 89 } 90 } 91 } 92 93 /** 94 * Adds a course module to the navigation node 95 * 96 * This is basically copied from function global_navigation::load_section_activities() 97 * because it is not accessible from outside. 98 * 99 * @param navigation_node $node 100 * @param cm_info $cm 101 * @return null|navigation_node 102 */ 103 protected function navigation_add_activity(navigation_node $node, $cm) { 104 if (!$cm->uservisible) { 105 return null; 106 } 107 $action = $cm->url; 108 if (!$action) { 109 // Do not add to navigation activity without url (i.e. labels). 110 return null; 111 } 112 $activityname = format_string($cm->name, true, array('context' => context_module::instance($cm->id))); 113 if ($cm->icon) { 114 $icon = new pix_icon($cm->icon, $cm->modfullname, $cm->iconcomponent); 115 } else { 116 $icon = new pix_icon('monologo', $cm->modfullname, $cm->modname); 117 } 118 $activitynode = $node->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon); 119 if (global_navigation::module_extends_navigation($cm->modname)) { 120 $activitynode->nodetype = navigation_node::NODETYPE_BRANCH; 121 } else { 122 $activitynode->nodetype = navigation_node::NODETYPE_LEAF; 123 } 124 return $activitynode; 125 } 126 127 /** 128 * Returns the list of blocks to be automatically added for the newly created course 129 * 130 * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT 131 * each of values is an array of block names (for left and right side columns) 132 */ 133 public function get_default_blocks() { 134 // No blocks for this format because course view page is not displayed anyway. 135 return array( 136 BLOCK_POS_LEFT => array(), 137 BLOCK_POS_RIGHT => array() 138 ); 139 } 140 141 /** 142 * Definitions of the additional options that this course format uses for course 143 * 144 * Singleactivity course format uses one option 'activitytype' 145 * 146 * @param bool $foreditform 147 * @return array of options 148 */ 149 public function course_format_options($foreditform = false) { 150 static $courseformatoptions = false; 151 152 $fetchtypes = $courseformatoptions === false; 153 $fetchtypes = $fetchtypes || ($foreditform && !isset($courseformatoptions['activitytype']['label'])); 154 155 if ($fetchtypes) { 156 $availabletypes = $this->get_supported_activities(); 157 if ($this->courseid) { 158 // The course exists. Test against the course. 159 $testcontext = context_course::instance($this->courseid); 160 } else if ($this->categoryid) { 161 // The course does not exist yet, but we have a category ID that we can test against. 162 $testcontext = context_coursecat::instance($this->categoryid); 163 } else { 164 // The course does not exist, and we somehow do not have a category. Test capabilities against the system context. 165 $testcontext = context_system::instance(); 166 } 167 foreach (array_keys($availabletypes) as $activity) { 168 $capability = "mod/{$activity}:addinstance"; 169 if (!has_capability($capability, $testcontext)) { 170 unset($availabletypes[$activity]); 171 } 172 } 173 } 174 175 if ($courseformatoptions === false) { 176 $config = get_config('format_singleactivity'); 177 $courseformatoptions = array( 178 'activitytype' => array( 179 'default' => $config->activitytype, 180 'type' => PARAM_TEXT, 181 ), 182 ); 183 184 if (!empty($availabletypes) && !isset($availabletypes[$config->activitytype])) { 185 $courseformatoptions['activitytype']['default'] = array_keys($availabletypes)[0]; 186 } 187 } 188 189 if ($foreditform && !isset($courseformatoptions['activitytype']['label'])) { 190 $courseformatoptionsedit = array( 191 'activitytype' => array( 192 'label' => new lang_string('activitytype', 'format_singleactivity'), 193 'help' => 'activitytype', 194 'help_component' => 'format_singleactivity', 195 'element_type' => 'select', 196 'element_attributes' => array($availabletypes), 197 ), 198 ); 199 $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit); 200 } 201 return $courseformatoptions; 202 } 203 204 /** 205 * Adds format options elements to the course/section edit form 206 * 207 * This function is called from {@link course_edit_form::definition_after_data()} 208 * 209 * Format singleactivity adds a warning when format of the course is about to be changed. 210 * 211 * @param MoodleQuickForm $mform form the elements are added to 212 * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form 213 * @return array array of references to the added form elements 214 */ 215 public function create_edit_form_elements(&$mform, $forsection = false) { 216 global $PAGE; 217 218 if (!$this->course && $submitvalues = $mform->getSubmitValues()) { 219 $this->categoryid = $submitvalues['category']; 220 } 221 222 $elements = parent::create_edit_form_elements($mform, $forsection); 223 if (!$forsection && ($course = $PAGE->course) && !empty($course->format) && 224 $course->format !== 'site' && $course->format !== 'singleactivity') { 225 // This is the existing course in other format, display a warning. 226 $element = $mform->addElement('static', '', '', 227 html_writer::tag('span', get_string('warningchangeformat', 'format_singleactivity'), 228 array('class' => 'error'))); 229 array_unshift($elements, $element); 230 } 231 return $elements; 232 } 233 234 /** 235 * Make sure that current active activity is in section 0 236 * 237 * All other activities are moved to section 1 that will be displayed as 'Orphaned'. 238 * It may be needed after the course format was changed or activitytype in 239 * course settings has been changed. 240 * 241 * @return null|cm_info current activity 242 */ 243 public function reorder_activities() { 244 course_create_sections_if_missing($this->courseid, array(0, 1)); 245 foreach ($this->get_sections() as $sectionnum => $section) { 246 if (($sectionnum && $section->visible) || 247 (!$sectionnum && !$section->visible)) { 248 // Make sure that 0 section is visible and all others are hidden. 249 set_section_visible($this->courseid, $sectionnum, $sectionnum == 0); 250 } 251 } 252 $modinfo = get_fast_modinfo($this->courseid); 253 254 // Find the current activity (first activity with the specified type in all course activities). 255 $activitytype = $this->get_activitytype(); 256 $activity = null; 257 if (!empty($activitytype)) { 258 foreach ($modinfo->sections as $sectionnum => $cmlist) { 259 foreach ($cmlist as $cmid) { 260 if ($modinfo->cms[$cmid]->modname === $activitytype) { 261 $activity = $modinfo->cms[$cmid]; 262 break 2; 263 } 264 } 265 } 266 } 267 268 // Make sure the current activity is in the 0-section. 269 $changed = false; 270 if ($activity && $activity->sectionnum != 0) { 271 moveto_module($activity, $modinfo->get_section_info(0)); 272 $changed = true; 273 } 274 if ($activity && !$activity->visible) { 275 set_coursemodule_visible($activity->id, 1); 276 $changed = true; 277 } 278 if ($changed) { 279 // Cache was reset so get modinfo again. 280 $modinfo = get_fast_modinfo($this->courseid); 281 } 282 283 // Move all other activities into section 1 (the order must be kept). 284 $hasvisibleactivities = false; 285 $firstorphanedcm = null; 286 foreach ($modinfo->sections as $sectionnum => $cmlist) { 287 if ($sectionnum && !empty($cmlist) && $firstorphanedcm === null) { 288 $firstorphanedcm = reset($cmlist); 289 } 290 foreach ($cmlist as $cmid) { 291 if ($sectionnum > 1) { 292 moveto_module($modinfo->get_cm($cmid), $modinfo->get_section_info(1)); 293 } else if (!$hasvisibleactivities && $sectionnum == 1 && $modinfo->get_cm($cmid)->visible) { 294 $hasvisibleactivities = true; 295 } 296 } 297 } 298 if (!empty($modinfo->sections[0])) { 299 foreach ($modinfo->sections[0] as $cmid) { 300 if (!$activity || $cmid != $activity->id) { 301 moveto_module($modinfo->get_cm($cmid), $modinfo->get_section_info(1), $firstorphanedcm); 302 } 303 } 304 } 305 if ($hasvisibleactivities) { 306 set_section_visible($this->courseid, 1, false); 307 } 308 return $activity; 309 } 310 311 /** 312 * Returns the name of activity type used for this course 313 * 314 * @return string|null 315 */ 316 protected function get_activitytype() { 317 $options = $this->get_format_options(); 318 $availabletypes = $this->get_supported_activities(); 319 if (!empty($options['activitytype']) && 320 array_key_exists($options['activitytype'], $availabletypes)) { 321 return $options['activitytype']; 322 } else { 323 return null; 324 } 325 } 326 327 /** 328 * Returns the current activity if exists 329 * 330 * @return null|cm_info 331 */ 332 protected function get_activity() { 333 if ($this->activity === false) { 334 $this->activity = $this->reorder_activities(); 335 } 336 return $this->activity; 337 } 338 339 /** 340 * Get the activities supported by the format. 341 * 342 * Here we ignore the modules that do not have a page of their own, like the label. 343 * 344 * @return array array($module => $name of the module). 345 */ 346 public static function get_supported_activities() { 347 $availabletypes = get_module_types_names(); 348 foreach ($availabletypes as $module => $name) { 349 if (plugin_supports('mod', $module, FEATURE_NO_VIEW_LINK, false)) { 350 unset($availabletypes[$module]); 351 } 352 } 353 return $availabletypes; 354 } 355 356 /** 357 * Checks if the current user can add the activity of the specified type to this course. 358 * 359 * @return bool 360 */ 361 protected function can_add_activity() { 362 global $CFG; 363 if (!($modname = $this->get_activitytype())) { 364 return false; 365 } 366 if (!has_capability('moodle/course:manageactivities', context_course::instance($this->courseid))) { 367 return false; 368 } 369 if (!course_allowed_module($this->get_course(), $modname)) { 370 return false; 371 } 372 $libfile = "$CFG->dirroot/mod/$modname/lib.php"; 373 if (!file_exists($libfile)) { 374 return null; 375 } 376 return true; 377 } 378 379 /** 380 * Checks if the activity type has multiple items in the activity chooser. 381 * 382 * @return bool|null (null if the check is not possible) 383 */ 384 public function activity_has_subtypes() { 385 global $USER; 386 if (!($modname = $this->get_activitytype())) { 387 return null; 388 } 389 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 390 $metadata = $contentitemservice->get_content_items_for_user_in_course($USER, $this->get_course()); 391 392 // If there are multiple items originating from this mod_xxx component, then it's deemed to have subtypes. 393 // If there is only 1 item, but it's not a reference to the core content item for the module, then it's also deemed to 394 // have subtypes. 395 $count = 0; 396 foreach ($metadata as $key => $moduledata) { 397 if ('mod_'.$modname === $moduledata->componentname) { 398 $count ++; 399 } 400 } 401 if ($count > 1) { 402 return true; 403 } else { 404 // Get the single item. 405 $itemmetadata = $metadata[array_search('mod_' . $modname, array_column($metadata, 'componentname'))]; 406 $urlbase = new \moodle_url('/course/mod.php', ['id' => $this->get_course()->id]); 407 $referenceurl = new \moodle_url($urlbase, ['add' => $modname]); 408 if ($referenceurl->out(false) != $itemmetadata->link) { 409 return true; 410 } 411 } 412 return false; 413 } 414 415 /** 416 * Allows course format to execute code on moodle_page::set_course() 417 * 418 * This function is executed before the output starts. 419 * 420 * If everything is configured correctly, user is redirected from the 421 * default course view page to the activity view page. 422 * 423 * "Section 1" is the administrative page to manage orphaned activities 424 * 425 * If user is on course view page and there is no module added to the course 426 * and the user has 'moodle/course:manageactivities' capability, redirect to create module 427 * form. 428 * 429 * @param moodle_page $page instance of page calling set_course 430 */ 431 public function page_set_course(moodle_page $page) { 432 global $PAGE; 433 $page->add_body_class('format-'. $this->get_format()); 434 if ($PAGE == $page && $page->has_set_url() && 435 $page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) { 436 $edit = optional_param('edit', -1, PARAM_BOOL); 437 if (($edit == 0 || $edit == 1) && confirm_sesskey()) { 438 // This is a request to turn editing mode on or off, do not redirect here, /course/view.php will do redirection. 439 return; 440 } 441 $cm = $this->get_activity(); 442 $cursection = optional_param('section', null, PARAM_INT); 443 if (!empty($cursection) && has_capability('moodle/course:viewhiddensections', 444 context_course::instance($this->courseid))) { 445 // Display orphaned activities (course view page, section 1). 446 return; 447 } 448 if (!$this->get_activitytype()) { 449 if (has_capability('moodle/course:update', context_course::instance($this->courseid))) { 450 // Teacher is redirected to edit course page. 451 $url = new moodle_url('/course/edit.php', array('id' => $this->courseid)); 452 redirect($url, get_string('erroractivitytype', 'format_singleactivity')); 453 } else { 454 // Student sees an empty course page. 455 return; 456 } 457 } 458 if ($cm === null) { 459 if ($this->can_add_activity()) { 460 // This is a user who has capability to create an activity. 461 if ($this->activity_has_subtypes()) { 462 // Activity has multiple items in the activity chooser, it can not be added automatically. 463 if (optional_param('addactivity', 0, PARAM_INT)) { 464 return; 465 } else { 466 $url = new moodle_url('/course/view.php', array('id' => $this->courseid, 'addactivity' => 1)); 467 redirect($url); 468 } 469 } 470 // Redirect to the add activity form. 471 $url = new moodle_url('/course/mod.php', array('id' => $this->courseid, 472 'section' => 0, 'sesskey' => sesskey(), 'add' => $this->get_activitytype())); 473 redirect($url); 474 } else { 475 // Student views an empty course page. 476 return; 477 } 478 } else if (!$cm->uservisible || !$cm->url) { 479 // Activity is set but not visible to current user or does not have url. 480 // Display course page (either empty or with availability restriction info). 481 return; 482 } else { 483 // Everything is set up and accessible, redirect to the activity page! 484 redirect($cm->url); 485 } 486 } 487 } 488 489 /** 490 * Allows course format to execute code on moodle_page::set_cm() 491 * 492 * If we are inside the main module for this course, remove extra node level 493 * from navigation: substitute course node with activity node, move all children 494 * 495 * @param moodle_page $page instance of page calling set_cm 496 */ 497 public function page_set_cm(moodle_page $page) { 498 global $PAGE; 499 parent::page_set_cm($page); 500 if ($PAGE == $page && ($cm = $this->get_activity()) && 501 $cm->uservisible && 502 ($cm->id === $page->cm->id) && 503 ($activitynode = $page->navigation->find($cm->id, navigation_node::TYPE_ACTIVITY)) && 504 ($node = $page->navigation->find($page->course->id, navigation_node::TYPE_COURSE))) { 505 // Substitute course node with activity node, move all children. 506 $node->action = $activitynode->action; 507 $node->type = $activitynode->type; 508 $node->id = $activitynode->id; 509 $node->key = $activitynode->key; 510 $node->isactive = $node->isactive || $activitynode->isactive; 511 $node->icon = null; 512 if ($activitynode->children->count()) { 513 foreach ($activitynode->children as &$child) { 514 $child->remove(); 515 $node->add_node($child); 516 } 517 } else { 518 $node->search_for_active_node(); 519 } 520 $activitynode->remove(); 521 } 522 } 523 524 /** 525 * Returns true if the course has a front page. 526 * 527 * @return boolean false 528 */ 529 public function has_view_page() { 530 return false; 531 } 532 533 /** 534 * Return the plugin configs for external functions. 535 * 536 * @return array the list of configuration settings 537 * @since Moodle 3.5 538 */ 539 public function get_config_for_external() { 540 // Return everything (nothing to hide). 541 return $this->get_format_options(); 542 } 543 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body