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 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Renderer for use with the course section and all the goodness that falls 20 * within it. 21 * 22 * This renderer should contain methods useful to courses, and categories. 23 * 24 * @package moodlecore 25 * @copyright 2010 Sam Hemelryk 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 /** 30 * The core course renderer 31 * 32 * Can be retrieved with the following: 33 * $renderer = $PAGE->get_renderer('core','course'); 34 */ 35 class core_course_renderer extends plugin_renderer_base { 36 const COURSECAT_SHOW_COURSES_NONE = 0; /* do not show courses at all */ 37 const COURSECAT_SHOW_COURSES_COUNT = 5; /* do not show courses but show number of courses next to category name */ 38 const COURSECAT_SHOW_COURSES_COLLAPSED = 10; 39 const COURSECAT_SHOW_COURSES_AUTO = 15; /* will choose between collapsed and expanded automatically */ 40 const COURSECAT_SHOW_COURSES_EXPANDED = 20; 41 const COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT = 30; 42 43 const COURSECAT_TYPE_CATEGORY = 0; 44 const COURSECAT_TYPE_COURSE = 1; 45 46 /** 47 * A cache of strings 48 * @var stdClass 49 */ 50 protected $strings; 51 52 /** 53 * Whether a category content is being initially rendered with children. This is mainly used by the 54 * core_course_renderer::corsecat_tree() to render the appropriate action for the Expand/Collapse all link on 55 * page load. 56 * @var bool 57 */ 58 protected $categoryexpandedonload = false; 59 60 /** 61 * Override the constructor so that we can initialise the string cache 62 * 63 * @param moodle_page $page 64 * @param string $target 65 */ 66 public function __construct(moodle_page $page, $target) { 67 $this->strings = new stdClass; 68 parent::__construct($page, $target); 69 } 70 71 /** 72 * @deprecated since 3.2 73 */ 74 protected function add_modchoosertoggle() { 75 throw new coding_exception('core_course_renderer::add_modchoosertoggle() can not be used anymore.'); 76 } 77 78 /** 79 * Renders course info box. 80 * 81 * @param stdClass $course 82 * @return string 83 */ 84 public function course_info_box(stdClass $course) { 85 $content = ''; 86 $content .= $this->output->box_start('generalbox info'); 87 $chelper = new coursecat_helper(); 88 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED); 89 $content .= $this->coursecat_coursebox($chelper, $course); 90 $content .= $this->output->box_end(); 91 return $content; 92 } 93 94 /** 95 * Renderers a structured array of courses and categories into a nice XHTML tree structure. 96 * 97 * @deprecated since 2.5 98 * 99 * Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5 100 * 101 * @param array $ignored argument ignored 102 * @return string 103 */ 104 public final function course_category_tree(array $ignored) { 105 debugging('Function core_course_renderer::course_category_tree() is deprecated, please use frontpage_combo_list()', DEBUG_DEVELOPER); 106 return $this->frontpage_combo_list(); 107 } 108 109 /** 110 * Renderers a category for use with course_category_tree 111 * 112 * @deprecated since 2.5 113 * 114 * Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5 115 * 116 * @param array $category 117 * @param int $depth 118 * @return string 119 */ 120 protected final function course_category_tree_category(stdClass $category, $depth=1) { 121 debugging('Function core_course_renderer::course_category_tree_category() is deprecated', DEBUG_DEVELOPER); 122 return ''; 123 } 124 125 /** 126 * Render a modchooser. 127 * 128 * @param renderable $modchooser The chooser. 129 * @return string 130 */ 131 public function render_modchooser(renderable $modchooser) { 132 return $this->render_from_template('core_course/modchooser', $modchooser->export_for_template($this)); 133 } 134 135 /** 136 * Build the HTML for the module chooser javascript popup 137 * 138 * @param array $modules A set of modules as returned form @see 139 * get_module_metadata 140 * @param object $course The course that will be displayed 141 * @return string The composed HTML for the module 142 */ 143 public function course_modchooser($modules, $course) { 144 debugging('course_modchooser() is deprecated. Please use course_activitychooser() instead.', DEBUG_DEVELOPER); 145 146 return $this->course_activitychooser($course->id); 147 } 148 149 /** 150 * Build the HTML for the module chooser javascript popup. 151 * 152 * @param int $courseid The course id to fetch modules for. 153 * @return string 154 */ 155 public function course_activitychooser($courseid) { 156 157 if (!$this->page->requires->should_create_one_time_item_now('core_course_modchooser')) { 158 return ''; 159 } 160 161 // Build an object of config settings that we can then hook into in the Activity Chooser. 162 $chooserconfig = (object) [ 163 'tabmode' => get_config('core', 'activitychoosertabmode'), 164 ]; 165 $this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid, $chooserconfig]); 166 167 return ''; 168 } 169 170 /** 171 * Build the HTML for a specified set of modules 172 * 173 * @param array $modules A set of modules as used by the 174 * course_modchooser_module function 175 * @return string The composed HTML for the module 176 */ 177 protected function course_modchooser_module_types($modules) { 178 debugging('Method core_course_renderer::course_modchooser_module_types() is deprecated, ' . 179 'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER); 180 return ''; 181 } 182 183 /** 184 * Return the HTML for the specified module adding any required classes 185 * 186 * @param object $module An object containing the title, and link. An 187 * icon, and help text may optionally be specified. If the module 188 * contains subtypes in the types option, then these will also be 189 * displayed. 190 * @param array $classes Additional classes to add to the encompassing 191 * div element 192 * @return string The composed HTML for the module 193 */ 194 protected function course_modchooser_module($module, $classes = array('option')) { 195 debugging('Method core_course_renderer::course_modchooser_module() is deprecated, ' . 196 'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER); 197 return ''; 198 } 199 200 protected function course_modchooser_title($title, $identifier = null) { 201 debugging('Method core_course_renderer::course_modchooser_title() is deprecated, ' . 202 'see core_course_renderer::render_modchooser().', DEBUG_DEVELOPER); 203 return ''; 204 } 205 206 /** 207 * Renders HTML for displaying the sequence of course module editing buttons 208 * 209 * @see course_get_cm_edit_actions() 210 * 211 * @param action_link[] $actions Array of action_link objects 212 * @param cm_info $mod The module we are displaying actions for. 213 * @param array $displayoptions additional display options: 214 * ownerselector => A JS/CSS selector that can be used to find an cm node. 215 * If specified the owning node will be given the class 'action-menu-shown' when the action 216 * menu is being displayed. 217 * constraintselector => A JS/CSS selector that can be used to find the parent node for which to constrain 218 * the action menu to when it is being displayed. 219 * donotenhance => If set to true the action menu that gets displayed won't be enhanced by JS. 220 * @return string 221 */ 222 public function course_section_cm_edit_actions($actions, cm_info $mod = null, $displayoptions = array()) { 223 global $CFG; 224 225 if (empty($actions)) { 226 return ''; 227 } 228 229 if (isset($displayoptions['ownerselector'])) { 230 $ownerselector = $displayoptions['ownerselector']; 231 } else if ($mod) { 232 $ownerselector = '#module-'.$mod->id; 233 } else { 234 debugging('You should upgrade your call to '.__FUNCTION__.' and provide $mod', DEBUG_DEVELOPER); 235 $ownerselector = 'li.activity'; 236 } 237 238 if (isset($displayoptions['constraintselector'])) { 239 $constraint = $displayoptions['constraintselector']; 240 } else { 241 $constraint = '.course-content'; 242 } 243 244 $menu = new action_menu(); 245 $menu->set_owner_selector($ownerselector); 246 $menu->set_constraint($constraint); 247 $menu->set_alignment(action_menu::TR, action_menu::BR); 248 $menu->set_menu_trigger(get_string('edit')); 249 250 foreach ($actions as $action) { 251 if ($action instanceof action_menu_link) { 252 $action->add_class('cm-edit-action'); 253 } 254 $menu->add($action); 255 } 256 $menu->attributes['class'] .= ' section-cm-edit-actions commands'; 257 258 // Prioritise the menu ahead of all other actions. 259 $menu->prioritise = true; 260 261 return $this->render($menu); 262 } 263 264 /** 265 * Renders HTML for the menus to add activities and resources to the current course 266 * 267 * Renders the ajax control (the link which when clicked produces the activity chooser modal). No noscript fallback. 268 * 269 * @param stdClass $course 270 * @param int $section relative section number (field course_sections.section) 271 * @param int $sectionreturn The section to link back to 272 * @param array $displayoptions additional display options, for example blocks add 273 * option 'inblock' => true, suggesting to display controls vertically 274 * @return string 275 */ 276 function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) { 277 // Check to see if user can add menus. 278 if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id)) 279 || !$this->page->user_is_editing()) { 280 return ''; 281 } 282 283 $straddeither = get_string('addresourceoractivity'); 284 285 $ajaxcontrol = html_writer::start_tag('div', array('class' => 'mdl-right')); 286 $ajaxcontrol .= html_writer::start_tag('div', array('class' => 'section-modchooser')); 287 288 $icon = $this->output->pix_icon('t/add', ''); 289 $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text')); 290 291 $ajaxcontrol .= html_writer::tag('button', $icon . $span, [ 292 'class' => 'section-modchooser-link btn btn-link', 293 'data-action' => 'open-chooser', 294 'data-sectionid' => $section, 295 'data-sectionreturnid' => $sectionreturn, 296 ]); 297 298 $ajaxcontrol .= html_writer::end_tag('div'); 299 $ajaxcontrol .= html_writer::end_tag('div'); 300 301 // Load the JS for the modal. 302 $this->course_activitychooser($course->id); 303 304 return $ajaxcontrol; 305 } 306 307 /** 308 * Render the deprecated nonajax activity chooser. 309 * 310 * @deprecated since Moodle 3.11 311 * 312 * @todo MDL-71331 deprecate this function 313 * @param stdClass $course the course object 314 * @param int $section relative section number (field course_sections.section) 315 * @param int $sectionreturn The section to link back to 316 * @param array $displayoptions additional display options, for example blocks add 317 * option 'inblock' => true, suggesting to display controls vertically 318 * @return string 319 */ 320 private function course_section_add_cm_control_nonajax($course, $section, $sectionreturn = null, 321 $displayoptions = array()): string { 322 global $USER; 323 324 $vertical = !empty($displayoptions['inblock']); 325 326 // Check to see if user can add menus. 327 if ( 328 !has_capability('moodle/course:manageactivities', context_course::instance($course->id)) 329 || !$this->page->user_is_editing() 330 ) { 331 return ''; 332 } 333 334 debugging('non-js dropdowns are deprecated.', DEBUG_DEVELOPER); 335 // Retrieve all modules with associated metadata. 336 $contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service(); 337 $urlparams = ['section' => $section]; 338 if (!is_null($sectionreturn)) { 339 $urlparams['sr'] = $sectionreturn; 340 } 341 $modules = $contentitemservice->get_content_items_for_user_in_course($USER, $course, $urlparams); 342 343 // Return if there are no content items to add. 344 if (empty($modules)) { 345 return ''; 346 } 347 348 // We'll sort resources and activities into two lists. 349 $activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array()); 350 351 foreach ($modules as $module) { 352 $activityclass = MOD_CLASS_ACTIVITY; 353 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) { 354 $activityclass = MOD_CLASS_RESOURCE; 355 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) { 356 // System modules cannot be added by user, do not add to dropdown. 357 continue; 358 } 359 $link = $module->link; 360 $activities[$activityclass][$link] = $module->title; 361 } 362 363 $straddactivity = get_string('addactivity'); 364 $straddresource = get_string('addresource'); 365 $sectionname = get_section_name($course, $section); 366 $strresourcelabel = get_string('addresourcetosection', null, $sectionname); 367 $stractivitylabel = get_string('addactivitytosection', null, $sectionname); 368 369 $nonajaxcontrol = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' 370 . $section)); 371 372 if (!$vertical) { 373 $nonajaxcontrol .= html_writer::start_tag('div', array('class' => 'horizontal')); 374 } 375 376 if (!empty($activities[MOD_CLASS_RESOURCE])) { 377 $select = new url_select($activities[MOD_CLASS_RESOURCE], '', array('' => $straddresource), "ressection$section"); 378 $select->set_help_icon('resources'); 379 $select->set_label($strresourcelabel, array('class' => 'accesshide')); 380 $nonajaxcontrol .= $this->output->render($select); 381 } 382 383 if (!empty($activities[MOD_CLASS_ACTIVITY])) { 384 $select = new url_select($activities[MOD_CLASS_ACTIVITY], '', array('' => $straddactivity), "section$section"); 385 $select->set_help_icon('activities'); 386 $select->set_label($stractivitylabel, array('class' => 'accesshide')); 387 $nonajaxcontrol .= $this->output->render($select); 388 } 389 390 if (!$vertical) { 391 $nonajaxcontrol .= html_writer::end_tag('div'); 392 } 393 394 $nonajaxcontrol .= html_writer::end_tag('div'); 395 396 return $nonajaxcontrol; 397 } 398 399 /** 400 * Renders html to display a course search form 401 * 402 * @param string $value default value to populate the search field 403 * @return string 404 */ 405 public function course_search_form($value = '') { 406 407 $data = [ 408 'action' => \core_search\manager::get_course_search_url(), 409 'btnclass' => 'btn-primary', 410 'inputname' => 'q', 411 'searchstring' => get_string('searchcourses'), 412 'hiddenfields' => (object) ['name' => 'areaids', 'value' => 'core_course-course'], 413 'query' => $value 414 ]; 415 return $this->render_from_template('core/search_input', $data); 416 } 417 418 /** 419 * Renders html for completion box on course page 420 * 421 * If completion is disabled, returns empty string 422 * If completion is automatic, returns an icon of the current completion state 423 * If completion is manual, returns a form (with an icon inside) that allows user to 424 * toggle completion 425 * 426 * @deprecated since Moodle 3.11 427 * @todo MDL-71183 Final deprecation in Moodle 4.3. 428 * @see \core_renderer::activity_information 429 * 430 * @param stdClass $course course object 431 * @param completion_info $completioninfo completion info for the course, it is recommended 432 * to fetch once for all modules in course/section for performance 433 * @param cm_info $mod module to show completion for 434 * @param array $displayoptions display options, not used in core 435 * @return string 436 */ 437 public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) { 438 global $CFG, $DB, $USER; 439 440 debugging(__FUNCTION__ . ' is deprecated and is being replaced by the activity_information output component.', 441 DEBUG_DEVELOPER); 442 443 $output = ''; 444 445 $istrackeduser = $completioninfo->is_tracked_user($USER->id); 446 $isediting = $this->page->user_is_editing(); 447 448 if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) { 449 return $output; 450 } 451 if ($completioninfo === null) { 452 $completioninfo = new completion_info($course); 453 } 454 $completion = $completioninfo->is_enabled($mod); 455 456 if ($completion == COMPLETION_TRACKING_NONE) { 457 if ($isediting) { 458 $output .= html_writer::span(' ', 'filler'); 459 } 460 return $output; 461 } 462 463 $completionicon = ''; 464 465 if ($isediting || !$istrackeduser) { 466 switch ($completion) { 467 case COMPLETION_TRACKING_MANUAL : 468 $completionicon = 'manual-enabled'; break; 469 case COMPLETION_TRACKING_AUTOMATIC : 470 $completionicon = 'auto-enabled'; break; 471 } 472 } else { 473 $completiondata = $completioninfo->get_data($mod, true); 474 if ($completion == COMPLETION_TRACKING_MANUAL) { 475 switch($completiondata->completionstate) { 476 case COMPLETION_INCOMPLETE: 477 $completionicon = 'manual-n' . ($completiondata->overrideby ? '-override' : ''); 478 break; 479 case COMPLETION_COMPLETE: 480 $completionicon = 'manual-y' . ($completiondata->overrideby ? '-override' : ''); 481 break; 482 } 483 } else { // Automatic 484 switch($completiondata->completionstate) { 485 case COMPLETION_INCOMPLETE: 486 $completionicon = 'auto-n' . ($completiondata->overrideby ? '-override' : ''); 487 break; 488 case COMPLETION_COMPLETE: 489 $completionicon = 'auto-y' . ($completiondata->overrideby ? '-override' : ''); 490 break; 491 case COMPLETION_COMPLETE_PASS: 492 $completionicon = 'auto-pass'; break; 493 case COMPLETION_COMPLETE_FAIL: 494 $completionicon = 'auto-fail'; break; 495 } 496 } 497 } 498 if ($completionicon) { 499 $formattedname = html_entity_decode($mod->get_formatted_name(), ENT_QUOTES, 'UTF-8'); 500 if (!$isediting && $istrackeduser && $completiondata->overrideby) { 501 $args = new stdClass(); 502 $args->modname = $formattedname; 503 $overridebyuser = \core_user::get_user($completiondata->overrideby, '*', MUST_EXIST); 504 $args->overrideuser = fullname($overridebyuser); 505 $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $args); 506 } else { 507 $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname); 508 } 509 510 if ($isediting || !$istrackeduser || !has_capability('moodle/course:togglecompletion', $mod->context)) { 511 // When editing, the icon is just an image. 512 $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '', 513 array('title' => $imgalt, 'class' => 'iconsmall')); 514 $output .= html_writer::tag('span', $this->output->render($completionpixicon), 515 array('class' => 'autocompletion')); 516 } else if ($completion == COMPLETION_TRACKING_MANUAL) { 517 $newstate = 518 $completiondata->completionstate == COMPLETION_COMPLETE 519 ? COMPLETION_INCOMPLETE 520 : COMPLETION_COMPLETE; 521 // In manual mode the icon is a toggle form... 522 523 // If this completion state is used by the 524 // conditional activities system, we need to turn 525 // off the JS. 526 $extraclass = ''; 527 if (!empty($CFG->enableavailability) && 528 core_availability\info::completion_value_used($course, $mod->id)) { 529 $extraclass = ' preventjs'; 530 } 531 $output .= html_writer::start_tag('form', array('method' => 'post', 532 'action' => new moodle_url('/course/togglecompletion.php'), 533 'class' => 'togglecompletion'. $extraclass)); 534 $output .= html_writer::start_tag('div'); 535 $output .= html_writer::empty_tag('input', array( 536 'type' => 'hidden', 'name' => 'id', 'value' => $mod->id)); 537 $output .= html_writer::empty_tag('input', array( 538 'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); 539 $output .= html_writer::empty_tag('input', array( 540 'type' => 'hidden', 'name' => 'modulename', 'value' => $formattedname)); 541 $output .= html_writer::empty_tag('input', array( 542 'type' => 'hidden', 'name' => 'completionstate', 'value' => $newstate)); 543 $output .= html_writer::tag('button', 544 $this->output->pix_icon('i/completion-' . $completionicon, $imgalt), 545 array('class' => 'btn btn-link', 'aria-live' => 'assertive')); 546 $output .= html_writer::end_tag('div'); 547 $output .= html_writer::end_tag('form'); 548 } else { 549 // In auto mode, the icon is just an image. 550 $completionpixicon = new pix_icon('i/completion-'.$completionicon, $imgalt, '', 551 array('title' => $imgalt)); 552 $output .= html_writer::tag('span', $this->output->render($completionpixicon), 553 array('class' => 'autocompletion')); 554 } 555 } 556 return $output; 557 } 558 559 /** 560 * Checks if course module has any conditions that may make it unavailable for 561 * all or some of the students 562 * 563 * This function is internal and is only used to create CSS classes for the module name/text 564 * 565 * @param cm_info $mod 566 * @return bool 567 */ 568 protected function is_cm_conditionally_hidden(cm_info $mod) { 569 global $CFG; 570 $conditionalhidden = false; 571 if (!empty($CFG->enableavailability)) { 572 $info = new \core_availability\info_module($mod); 573 $conditionalhidden = !$info->is_available_for_all(); 574 } 575 return $conditionalhidden; 576 } 577 578 /** 579 * Renders html to display a name with the link to the course module on a course page 580 * 581 * If module is unavailable for user but still needs to be displayed 582 * in the list, just the name is returned without a link 583 * 584 * Note, that for course modules that never have separate pages (i.e. labels) 585 * this function return an empty string 586 * 587 * @param cm_info $mod 588 * @param array $displayoptions 589 * @return string 590 */ 591 public function course_section_cm_name(cm_info $mod, $displayoptions = array()) { 592 if (!$mod->is_visible_on_course_page() || !$mod->url) { 593 // Nothing to be displayed to the user. 594 return ''; 595 } 596 597 list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod); 598 $groupinglabel = $mod->get_grouping_label($textclasses); 599 600 // Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()} 601 // to get the display title of the activity. 602 $tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions); 603 return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output)) . 604 $groupinglabel; 605 } 606 607 /** 608 * Returns the CSS classes for the activity name/content 609 * 610 * For items which are hidden, unavailable or stealth but should be displayed 611 * to current user ($mod->is_visible_on_course_page()), we show those as dimmed. 612 * Students will also see as dimmed activities names that are not yet available 613 * but should still be displayed (without link) with availability info. 614 * 615 * @param cm_info $mod 616 * @return array array of two elements ($linkclasses, $textclasses) 617 */ 618 protected function course_section_cm_classes(cm_info $mod) { 619 $linkclasses = ''; 620 $textclasses = ''; 621 if ($mod->uservisible) { 622 $conditionalhidden = $this->is_cm_conditionally_hidden($mod); 623 $accessiblebutdim = (!$mod->visible || $conditionalhidden) && 624 has_capability('moodle/course:viewhiddenactivities', $mod->context); 625 if ($accessiblebutdim) { 626 $linkclasses .= ' dimmed'; 627 $textclasses .= ' dimmed_text'; 628 if ($conditionalhidden) { 629 $linkclasses .= ' conditionalhidden'; 630 $textclasses .= ' conditionalhidden'; 631 } 632 } 633 if ($mod->is_stealth()) { 634 // Stealth activity is the one that is not visible on course page. 635 // It still may be displayed to the users who can manage it. 636 $linkclasses .= ' stealth'; 637 $textclasses .= ' stealth'; 638 } 639 } else { 640 $linkclasses .= ' dimmed'; 641 $textclasses .= ' dimmed dimmed_text'; 642 } 643 return array($linkclasses, $textclasses); 644 } 645 646 /** 647 * Renders html to display a name with the link to the course module on a course page 648 * 649 * If module is unavailable for user but still needs to be displayed 650 * in the list, just the name is returned without a link 651 * 652 * Note, that for course modules that never have separate pages (i.e. labels) 653 * this function return an empty string 654 * 655 * @param cm_info $mod 656 * @param array $displayoptions 657 * @return string 658 */ 659 public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) { 660 $output = ''; 661 $url = $mod->url; 662 if (!$mod->is_visible_on_course_page() || !$url) { 663 // Nothing to be displayed to the user. 664 return $output; 665 } 666 667 //Accessibility: for files get description via icon, this is very ugly hack! 668 $instancename = $mod->get_formatted_name(); 669 $altname = $mod->modfullname; 670 // Avoid unnecessary duplication: if e.g. a forum name already 671 // includes the word forum (or Forum, etc) then it is unhelpful 672 // to include that in the accessible description that is added. 673 if (false !== strpos(core_text::strtolower($instancename), 674 core_text::strtolower($altname))) { 675 $altname = ''; 676 } 677 // File type after name, for alphabetic lists (screen reader). 678 if ($altname) { 679 $altname = get_accesshide(' '.$altname); 680 } 681 682 list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod); 683 684 // Get on-click attribute value if specified and decode the onclick - it 685 // has already been encoded for display (puke). 686 $onclick = htmlspecialchars_decode($mod->onclick, ENT_QUOTES); 687 688 // Display link itself. 689 $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(), 690 'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) . 691 html_writer::tag('span', $instancename . $altname, array('class' => 'instancename')); 692 if ($mod->uservisible) { 693 $output .= html_writer::link($url, $activitylink, array('class' => 'aalink' . $linkclasses, 'onclick' => $onclick)); 694 } else { 695 // We may be displaying this just in order to show information 696 // about visibility, without the actual link ($mod->is_visible_on_course_page()). 697 $output .= html_writer::tag('div', $activitylink, array('class' => $textclasses)); 698 } 699 return $output; 700 } 701 702 /** 703 * Renders html to display the module content on the course page (i.e. text of the labels) 704 * 705 * @param cm_info $mod 706 * @param array $displayoptions 707 * @return string 708 */ 709 public function course_section_cm_text(cm_info $mod, $displayoptions = array()) { 710 $output = ''; 711 if (!$mod->is_visible_on_course_page()) { 712 // nothing to be displayed to the user 713 return $output; 714 } 715 $content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true)); 716 list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod); 717 if ($mod->url && $mod->uservisible) { 718 if ($content) { 719 // If specified, display extra content after link. 720 $output = html_writer::tag('div', $content, array('class' => 721 trim('contentafterlink ' . $textclasses))); 722 } 723 } else { 724 $groupinglabel = $mod->get_grouping_label($textclasses); 725 726 // No link, so display only content. 727 $output = html_writer::tag('div', $content . $groupinglabel, 728 array('class' => 'contentwithoutlink ' . $textclasses)); 729 } 730 return $output; 731 } 732 733 /** 734 * Displays availability info for a course section or course module 735 * 736 * @param string $text 737 * @param string $additionalclasses 738 * @return string 739 */ 740 public function availability_info($text, $additionalclasses = '') { 741 742 $data = ['text' => $text, 'classes' => $additionalclasses]; 743 $additionalclasses = array_filter(explode(' ', $additionalclasses)); 744 745 if (in_array('ishidden', $additionalclasses)) { 746 $data['ishidden'] = 1; 747 748 } else if (in_array('isstealth', $additionalclasses)) { 749 $data['isstealth'] = 1; 750 751 } else if (in_array('isrestricted', $additionalclasses)) { 752 $data['isrestricted'] = 1; 753 754 if (in_array('isfullinfo', $additionalclasses)) { 755 $data['isfullinfo'] = 1; 756 } 757 } 758 759 return $this->render_from_template('core/availability_info', $data); 760 } 761 762 /** 763 * Renders HTML to show course module availability information (for someone who isn't allowed 764 * to see the activity itself, or for staff) 765 * 766 * @param cm_info $mod 767 * @param array $displayoptions 768 * @return string 769 */ 770 public function course_section_cm_availability(cm_info $mod, $displayoptions = array()) { 771 global $CFG; 772 $output = ''; 773 if (!$mod->is_visible_on_course_page()) { 774 return $output; 775 } 776 if (!$mod->uservisible) { 777 // this is a student who is not allowed to see the module but might be allowed 778 // to see availability info (i.e. "Available from ...") 779 if (!empty($mod->availableinfo)) { 780 $formattedinfo = \core_availability\info::format_info( 781 $mod->availableinfo, $mod->get_course()); 782 $output = $this->availability_info($formattedinfo, 'isrestricted'); 783 } 784 return $output; 785 } 786 // this is a teacher who is allowed to see module but still should see the 787 // information that module is not available to all/some students 788 $modcontext = context_module::instance($mod->id); 789 $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext); 790 if ($canviewhidden && !$mod->visible) { 791 // This module is hidden but current user has capability to see it. 792 // Do not display the availability info if the whole section is hidden. 793 if ($mod->get_section_info()->visible) { 794 $output .= $this->availability_info(get_string('hiddenfromstudents'), 'ishidden'); 795 } 796 } else if ($mod->is_stealth()) { 797 // This module is available but is normally not displayed on the course page 798 // (this user can see it because they can manage it). 799 $output .= $this->availability_info(get_string('hiddenoncoursepage'), 'isstealth'); 800 } 801 if ($canviewhidden && !empty($CFG->enableavailability)) { 802 // Display information about conditional availability. 803 // Don't add availability information if user is not editing and activity is hidden. 804 if ($mod->visible || $this->page->user_is_editing()) { 805 $hidinfoclass = 'isrestricted isfullinfo'; 806 if (!$mod->visible) { 807 $hidinfoclass .= ' hide'; 808 } 809 $ci = new \core_availability\info_module($mod); 810 $fullinfo = $ci->get_full_information(); 811 if ($fullinfo) { 812 $formattedinfo = \core_availability\info::format_info( 813 $fullinfo, $mod->get_course()); 814 $output .= $this->availability_info($formattedinfo, $hidinfoclass); 815 } 816 } 817 } 818 return $output; 819 } 820 821 /** 822 * Renders HTML to display one course module for display within a section. 823 * 824 * This function calls: 825 * {@link core_course_renderer::course_section_cm()} 826 * 827 * @param stdClass $course 828 * @param completion_info $completioninfo 829 * @param cm_info $mod 830 * @param int|null $sectionreturn 831 * @param array $displayoptions 832 * @return String 833 */ 834 public function course_section_cm_list_item($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) { 835 $output = ''; 836 if ($modulehtml = $this->course_section_cm($course, $completioninfo, $mod, $sectionreturn, $displayoptions)) { 837 $infoclass = ''; 838 if ((($course->enablecompletion == COMPLETION_ENABLED) && 839 ($course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS)) || !empty($course->showactivitydates)) { 840 // This will apply styles to the course homepage when the activity information output component is displayed. 841 $infoclass = 'hasinfo'; 842 } 843 $modclasses = 'activity ' . $mod->modname . ' modtype_' . $mod->modname . ' ' . $mod->extraclasses . ' ' . $infoclass; 844 $output .= html_writer::tag('li', $modulehtml, array('class' => $modclasses, 'id' => 'module-' . $mod->id)); 845 } 846 return $output; 847 } 848 849 /** 850 * Renders HTML to display one course module in a course section 851 * 852 * This includes link, content, availability, completion info and additional information 853 * that module type wants to display (i.e. number of unread forum posts) 854 * 855 * This function calls: 856 * {@link core_course_renderer::course_section_cm_name()} 857 * {@link core_course_renderer::course_section_cm_text()} 858 * {@link core_course_renderer::course_section_cm_availability()} 859 * {@link course_get_cm_edit_actions()} 860 * {@link core_course_renderer::course_section_cm_edit_actions()} 861 * 862 * @param stdClass $course 863 * @param completion_info $completioninfo 864 * @param cm_info $mod 865 * @param int|null $sectionreturn 866 * @param array $displayoptions 867 * @return string 868 */ 869 public function course_section_cm($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) { 870 global $USER; 871 872 $output = ''; 873 // We return empty string (because course module will not be displayed at all) 874 // if: 875 // 1) The activity is not visible to users 876 // and 877 // 2) The 'availableinfo' is empty, i.e. the activity was 878 // hidden in a way that leaves no info, such as using the 879 // eye icon. 880 if (!$mod->is_visible_on_course_page()) { 881 return $output; 882 } 883 884 $indentclasses = 'mod-indent'; 885 if (!empty($mod->indent)) { 886 $indentclasses .= ' mod-indent-'.$mod->indent; 887 if ($mod->indent > 15) { 888 $indentclasses .= ' mod-indent-huge'; 889 } 890 } 891 892 $output .= html_writer::start_tag('div'); 893 894 if ($this->page->user_is_editing()) { 895 $output .= course_get_cm_move($mod, $sectionreturn); 896 } 897 898 $output .= html_writer::start_tag('div', array('class' => 'mod-indent-outer w-100')); 899 900 // This div is used to indent the content. 901 $output .= html_writer::div('', $indentclasses); 902 903 // Start a wrapper for the actual content to keep the indentation consistent 904 $output .= html_writer::start_tag('div'); 905 906 // Display the link to the module (or do nothing if module has no url) 907 $cmname = $this->course_section_cm_name($mod, $displayoptions); 908 909 if (!empty($cmname)) { 910 // Start the div for the activity title, excluding the edit icons. 911 $output .= html_writer::start_tag('div', array('class' => 'activityinstance')); 912 $output .= $cmname; 913 914 915 // Module can put text after the link (e.g. forum unread) 916 $output .= $mod->afterlink; 917 918 // Closing the tag which contains everything but edit icons. Content part of the module should not be part of this. 919 $output .= html_writer::end_tag('div'); // .activityinstance 920 } 921 922 // If there is content but NO link (eg label), then display the 923 // content here (BEFORE any icons). In this case cons must be 924 // displayed after the content so that it makes more sense visually 925 // and for accessibility reasons, e.g. if you have a one-line label 926 // it should work similarly (at least in terms of ordering) to an 927 // activity. 928 $contentpart = $this->course_section_cm_text($mod, $displayoptions); 929 $url = $mod->url; 930 if (empty($url)) { 931 $output .= $contentpart; 932 } 933 934 $modicons = ''; 935 if ($this->page->user_is_editing()) { 936 $editactions = course_get_cm_edit_actions($mod, $mod->indent, $sectionreturn); 937 $modicons .= ' '. $this->course_section_cm_edit_actions($editactions, $mod, $displayoptions); 938 $modicons .= $mod->afterediticons; 939 } 940 941 if (!empty($modicons)) { 942 $output .= html_writer::div($modicons, 'actions'); 943 } 944 945 // Fetch completion details. 946 $showcompletionconditions = $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS; 947 $completiondetails = \core_completion\cm_completion_details::get_instance($mod, $USER->id, $showcompletionconditions); 948 $ismanualcompletion = $completiondetails->has_completion() && !$completiondetails->is_automatic(); 949 950 // Fetch activity dates. 951 $activitydates = []; 952 if ($course->showactivitydates) { 953 $activitydates = \core\activity_dates::get_dates_for_module($mod, $USER->id); 954 } 955 956 // Show the activity information if: 957 // - The course's showcompletionconditions setting is enabled; or 958 // - The activity tracks completion manually; or 959 // - There are activity dates to be shown. 960 if ($showcompletionconditions || $ismanualcompletion || $activitydates) { 961 $output .= $this->output->activity_information($mod, $completiondetails, $activitydates); 962 } 963 964 // Show availability info (if module is not available). 965 $output .= $this->course_section_cm_availability($mod, $displayoptions); 966 967 // If there is content AND a link, then display the content here 968 // (AFTER any icons). Otherwise it was displayed before 969 if (!empty($url)) { 970 $output .= $contentpart; 971 } 972 973 $output .= html_writer::end_tag('div'); // $indentclasses 974 975 // End of indentation div. 976 $output .= html_writer::end_tag('div'); 977 978 $output .= html_writer::end_tag('div'); 979 return $output; 980 } 981 982 /** 983 * Message displayed to the user when they try to access unavailable activity following URL 984 * 985 * This method is a very simplified version of {@link course_section_cm()} to be part of the error 986 * notification only. It also does not check if module is visible on course page or not. 987 * 988 * The message will be displayed inside notification! 989 * 990 * @param cm_info $cm 991 * @return string 992 */ 993 public function course_section_cm_unavailable_error_message(cm_info $cm) { 994 if ($cm->uservisible) { 995 return null; 996 } 997 if (!$cm->availableinfo) { 998 return get_string('activityiscurrentlyhidden'); 999 } 1000 1001 $altname = get_accesshide(' ' . $cm->modfullname); 1002 $name = html_writer::empty_tag('img', array('src' => $cm->get_icon_url(), 1003 'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) . 1004 html_writer::tag('span', ' '.$cm->get_formatted_name() . $altname, array('class' => 'instancename')); 1005 $formattedinfo = \core_availability\info::format_info($cm->availableinfo, $cm->get_course()); 1006 return html_writer::div($name, 'activityinstance-error') . 1007 html_writer::div($formattedinfo, 'availabilityinfo-error'); 1008 } 1009 1010 /** 1011 * Renders HTML to display a list of course modules in a course section 1012 * Also displays "move here" controls in Javascript-disabled mode 1013 * 1014 * This function calls {@link core_course_renderer::course_section_cm()} 1015 * 1016 * @param stdClass $course course object 1017 * @param int|stdClass|section_info $section relative section number or section object 1018 * @param int $sectionreturn section number to return to 1019 * @param int $displayoptions 1020 * @return void 1021 */ 1022 public function course_section_cm_list($course, $section, $sectionreturn = null, $displayoptions = array()) { 1023 global $USER; 1024 1025 $output = ''; 1026 $modinfo = get_fast_modinfo($course); 1027 if (is_object($section)) { 1028 $section = $modinfo->get_section_info($section->section); 1029 } else { 1030 $section = $modinfo->get_section_info($section); 1031 } 1032 $completioninfo = new completion_info($course); 1033 1034 // check if we are currently in the process of moving a module with JavaScript disabled 1035 $ismoving = $this->page->user_is_editing() && ismoving($course->id); 1036 if ($ismoving) { 1037 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'")); 1038 } 1039 1040 // Get the list of modules visible to user (excluding the module being moved if there is one) 1041 $moduleshtml = array(); 1042 if (!empty($modinfo->sections[$section->section])) { 1043 foreach ($modinfo->sections[$section->section] as $modnumber) { 1044 $mod = $modinfo->cms[$modnumber]; 1045 1046 if ($ismoving and $mod->id == $USER->activitycopy) { 1047 // do not display moving mod 1048 continue; 1049 } 1050 1051 if ($modulehtml = $this->course_section_cm_list_item($course, 1052 $completioninfo, $mod, $sectionreturn, $displayoptions)) { 1053 $moduleshtml[$modnumber] = $modulehtml; 1054 } 1055 } 1056 } 1057 1058 $sectionoutput = ''; 1059 if (!empty($moduleshtml) || $ismoving) { 1060 foreach ($moduleshtml as $modnumber => $modulehtml) { 1061 if ($ismoving) { 1062 $movingurl = new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey())); 1063 $sectionoutput .= html_writer::tag('li', 1064 html_writer::link($movingurl, '', array('title' => $strmovefull, 'class' => 'movehere')), 1065 array('class' => 'movehere')); 1066 } 1067 1068 $sectionoutput .= $modulehtml; 1069 } 1070 1071 if ($ismoving) { 1072 $movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey())); 1073 $sectionoutput .= html_writer::tag('li', 1074 html_writer::link($movingurl, '', array('title' => $strmovefull, 'class' => 'movehere')), 1075 array('class' => 'movehere')); 1076 } 1077 } 1078 1079 // Always output the section module list. 1080 $output .= html_writer::tag('ul', $sectionoutput, array('class' => 'section img-text')); 1081 1082 return $output; 1083 } 1084 1085 /** 1086 * Displays a custom list of courses with paging bar if necessary 1087 * 1088 * If $paginationurl is specified but $totalcount is not, the link 'View more' 1089 * appears under the list. 1090 * 1091 * If both $paginationurl and $totalcount are specified, and $totalcount is 1092 * bigger than count($courses), a paging bar is displayed above and under the 1093 * courses list. 1094 * 1095 * @param array $courses array of course records (or instances of core_course_list_element) to show on this page 1096 * @param bool $showcategoryname whether to add category name to the course description 1097 * @param string $additionalclasses additional CSS classes to add to the div.courses 1098 * @param moodle_url $paginationurl url to view more or url to form links to the other pages in paging bar 1099 * @param int $totalcount total number of courses on all pages, if omitted $paginationurl will be displayed as 'View more' link 1100 * @param int $page current page number (defaults to 0 referring to the first page) 1101 * @param int $perpage number of records per page (defaults to $CFG->coursesperpage) 1102 * @return string 1103 */ 1104 public function courses_list($courses, $showcategoryname = false, $additionalclasses = null, $paginationurl = null, $totalcount = null, $page = 0, $perpage = null) { 1105 global $CFG; 1106 // create instance of coursecat_helper to pass display options to function rendering courses list 1107 $chelper = new coursecat_helper(); 1108 if ($showcategoryname) { 1109 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT); 1110 } else { 1111 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED); 1112 } 1113 if ($totalcount !== null && $paginationurl !== null) { 1114 // add options to display pagination 1115 if ($perpage === null) { 1116 $perpage = $CFG->coursesperpage; 1117 } 1118 $chelper->set_courses_display_options(array( 1119 'limit' => $perpage, 1120 'offset' => ((int)$page) * $perpage, 1121 'paginationurl' => $paginationurl, 1122 )); 1123 } else if ($paginationurl !== null) { 1124 // add options to display 'View more' link 1125 $chelper->set_courses_display_options(array('viewmoreurl' => $paginationurl)); 1126 $totalcount = count($courses) + 1; // has to be bigger than count($courses) otherwise link will not be displayed 1127 } 1128 $chelper->set_attributes(array('class' => $additionalclasses)); 1129 $content = $this->coursecat_courses($chelper, $courses, $totalcount); 1130 return $content; 1131 } 1132 1133 /** 1134 * Returns HTML to display course name. 1135 * 1136 * @param coursecat_helper $chelper 1137 * @param core_course_list_element $course 1138 * @return string 1139 */ 1140 protected function course_name(coursecat_helper $chelper, core_course_list_element $course): string { 1141 $content = ''; 1142 if ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_EXPANDED) { 1143 $nametag = 'h3'; 1144 } else { 1145 $nametag = 'div'; 1146 } 1147 $coursename = $chelper->get_course_formatted_name($course); 1148 $coursenamelink = html_writer::link(new moodle_url('/course/view.php', ['id' => $course->id]), 1149 $coursename, ['class' => $course->visible ? 'aalink' : 'aalink dimmed']); 1150 $content .= html_writer::tag($nametag, $coursenamelink, ['class' => 'coursename']); 1151 // If we display course in collapsed form but the course has summary or course contacts, display the link to the info page. 1152 $content .= html_writer::start_tag('div', ['class' => 'moreinfo']); 1153 if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) { 1154 if ($course->has_summary() || $course->has_course_contacts() || $course->has_course_overviewfiles() 1155 || $course->has_custom_fields()) { 1156 $url = new moodle_url('/course/info.php', ['id' => $course->id]); 1157 $image = $this->output->pix_icon('i/info', $this->strings->summary); 1158 $content .= html_writer::link($url, $image, ['title' => $this->strings->summary]); 1159 // Make sure JS file to expand course content is included. 1160 $this->coursecat_include_js(); 1161 } 1162 } 1163 $content .= html_writer::end_tag('div'); 1164 return $content; 1165 } 1166 1167 /** 1168 * Returns HTML to display course enrolment icons. 1169 * 1170 * @param core_course_list_element $course 1171 * @return string 1172 */ 1173 protected function course_enrolment_icons(core_course_list_element $course): string { 1174 $content = ''; 1175 if ($icons = enrol_get_course_info_icons($course)) { 1176 $content .= html_writer::start_tag('div', ['class' => 'enrolmenticons']); 1177 foreach ($icons as $icon) { 1178 $content .= $this->render($icon); 1179 } 1180 $content .= html_writer::end_tag('div'); 1181 } 1182 return $content; 1183 } 1184 1185 /** 1186 * Displays one course in the list of courses. 1187 * 1188 * This is an internal function, to display an information about just one course 1189 * please use {@link core_course_renderer::course_info_box()} 1190 * 1191 * @param coursecat_helper $chelper various display options 1192 * @param core_course_list_element|stdClass $course 1193 * @param string $additionalclasses additional classes to add to the main <div> tag (usually 1194 * depend on the course position in list - first/last/even/odd) 1195 * @return string 1196 */ 1197 protected function coursecat_coursebox(coursecat_helper $chelper, $course, $additionalclasses = '') { 1198 if (!isset($this->strings->summary)) { 1199 $this->strings->summary = get_string('summary'); 1200 } 1201 if ($chelper->get_show_courses() <= self::COURSECAT_SHOW_COURSES_COUNT) { 1202 return ''; 1203 } 1204 if ($course instanceof stdClass) { 1205 $course = new core_course_list_element($course); 1206 } 1207 $content = ''; 1208 $classes = trim('coursebox clearfix '. $additionalclasses); 1209 if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) { 1210 $classes .= ' collapsed'; 1211 } 1212 1213 // .coursebox 1214 $content .= html_writer::start_tag('div', array( 1215 'class' => $classes, 1216 'data-courseid' => $course->id, 1217 'data-type' => self::COURSECAT_TYPE_COURSE, 1218 )); 1219 1220 $content .= html_writer::start_tag('div', array('class' => 'info')); 1221 $content .= $this->course_name($chelper, $course); 1222 $content .= $this->course_enrolment_icons($course); 1223 $content .= html_writer::end_tag('div'); 1224 1225 $content .= html_writer::start_tag('div', array('class' => 'content')); 1226 $content .= $this->coursecat_coursebox_content($chelper, $course); 1227 $content .= html_writer::end_tag('div'); 1228 1229 $content .= html_writer::end_tag('div'); // .coursebox 1230 return $content; 1231 } 1232 1233 /** 1234 * Returns HTML to display course summary. 1235 * 1236 * @param coursecat_helper $chelper 1237 * @param core_course_list_element $course 1238 * @return string 1239 */ 1240 protected function course_summary(coursecat_helper $chelper, core_course_list_element $course): string { 1241 $content = ''; 1242 if ($course->has_summary()) { 1243 $content .= html_writer::start_tag('div', ['class' => 'summary']); 1244 $content .= $chelper->get_course_formatted_summary($course, 1245 array('overflowdiv' => true, 'noclean' => true, 'para' => false)); 1246 $content .= html_writer::end_tag('div'); 1247 } 1248 return $content; 1249 } 1250 1251 /** 1252 * Returns HTML to display course contacts. 1253 * 1254 * @param core_course_list_element $course 1255 * @return string 1256 */ 1257 protected function course_contacts(core_course_list_element $course) { 1258 $content = ''; 1259 if ($course->has_course_contacts()) { 1260 $content .= html_writer::start_tag('ul', ['class' => 'teachers']); 1261 foreach ($course->get_course_contacts() as $coursecontact) { 1262 $rolenames = array_map(function ($role) { 1263 return $role->displayname; 1264 }, $coursecontact['roles']); 1265 $name = implode(", ", $rolenames).': '. 1266 html_writer::link(new moodle_url('/user/view.php', 1267 ['id' => $coursecontact['user']->id, 'course' => SITEID]), 1268 $coursecontact['username']); 1269 $content .= html_writer::tag('li', $name); 1270 } 1271 $content .= html_writer::end_tag('ul'); 1272 } 1273 return $content; 1274 } 1275 1276 /** 1277 * Returns HTML to display course overview files. 1278 * 1279 * @param core_course_list_element $course 1280 * @return string 1281 */ 1282 protected function course_overview_files(core_course_list_element $course): string { 1283 global $CFG; 1284 1285 $contentimages = $contentfiles = ''; 1286 foreach ($course->get_course_overviewfiles() as $file) { 1287 $isimage = $file->is_valid_image(); 1288 $url = moodle_url::make_file_url("$CFG->wwwroot/pluginfile.php", 1289 '/' . $file->get_contextid() . '/' . $file->get_component() . '/' . 1290 $file->get_filearea() . $file->get_filepath() . $file->get_filename(), !$isimage); 1291 if ($isimage) { 1292 $contentimages .= html_writer::tag('div', 1293 html_writer::empty_tag('img', ['src' => $url]), 1294 ['class' => 'courseimage']); 1295 } else { 1296 $image = $this->output->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle'); 1297 $filename = html_writer::tag('span', $image, ['class' => 'fp-icon']). 1298 html_writer::tag('span', $file->get_filename(), ['class' => 'fp-filename']); 1299 $contentfiles .= html_writer::tag('span', 1300 html_writer::link($url, $filename), 1301 ['class' => 'coursefile fp-filename-icon']); 1302 } 1303 } 1304 return $contentimages . $contentfiles; 1305 } 1306 1307 /** 1308 * Returns HTML to display course category name. 1309 * 1310 * @param coursecat_helper $chelper 1311 * @param core_course_list_element $course 1312 * @return string 1313 */ 1314 protected function course_category_name(coursecat_helper $chelper, core_course_list_element $course): string { 1315 $content = ''; 1316 // Display course category if necessary (for example in search results). 1317 if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT) { 1318 if ($cat = core_course_category::get($course->category, IGNORE_MISSING)) { 1319 $content .= html_writer::start_tag('div', ['class' => 'coursecat']); 1320 $content .= get_string('category').': '. 1321 html_writer::link(new moodle_url('/course/index.php', ['categoryid' => $cat->id]), 1322 $cat->get_formatted_name(), ['class' => $cat->visible ? '' : 'dimmed']); 1323 $content .= html_writer::end_tag('div'); 1324 } 1325 } 1326 return $content; 1327 } 1328 1329 /** 1330 * Returns HTML to display course custom fields. 1331 * 1332 * @param core_course_list_element $course 1333 * @return string 1334 */ 1335 protected function course_custom_fields(core_course_list_element $course): string { 1336 $content = ''; 1337 if ($course->has_custom_fields()) { 1338 $handler = core_course\customfield\course_handler::create(); 1339 $customfields = $handler->display_custom_fields_data($course->get_custom_fields()); 1340 $content .= \html_writer::tag('div', $customfields, ['class' => 'customfields-container']); 1341 } 1342 return $content; 1343 } 1344 1345 /** 1346 * Returns HTML to display course content (summary, course contacts and optionally category name) 1347 * 1348 * This method is called from coursecat_coursebox() and may be re-used in AJAX 1349 * 1350 * @param coursecat_helper $chelper various display options 1351 * @param stdClass|core_course_list_element $course 1352 * @return string 1353 */ 1354 protected function coursecat_coursebox_content(coursecat_helper $chelper, $course) { 1355 if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) { 1356 return ''; 1357 } 1358 if ($course instanceof stdClass) { 1359 $course = new core_course_list_element($course); 1360 } 1361 $content = $this->course_summary($chelper, $course); 1362 $content .= $this->course_overview_files($course); 1363 $content .= $this->course_contacts($course); 1364 $content .= $this->course_category_name($chelper, $course); 1365 $content .= $this->course_custom_fields($course); 1366 return $content; 1367 } 1368 1369 /** 1370 * Renders the list of courses 1371 * 1372 * This is internal function, please use {@link core_course_renderer::courses_list()} or another public 1373 * method from outside of the class 1374 * 1375 * If list of courses is specified in $courses; the argument $chelper is only used 1376 * to retrieve display options and attributes, only methods get_show_courses(), 1377 * get_courses_display_option() and get_and_erase_attributes() are called. 1378 * 1379 * @param coursecat_helper $chelper various display options 1380 * @param array $courses the list of courses to display 1381 * @param int|null $totalcount total number of courses (affects display mode if it is AUTO or pagination if applicable), 1382 * defaulted to count($courses) 1383 * @return string 1384 */ 1385 protected function coursecat_courses(coursecat_helper $chelper, $courses, $totalcount = null) { 1386 global $CFG; 1387 if ($totalcount === null) { 1388 $totalcount = count($courses); 1389 } 1390 if (!$totalcount) { 1391 // Courses count is cached during courses retrieval. 1392 return ''; 1393 } 1394 1395 if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_AUTO) { 1396 // In 'auto' course display mode we analyse if number of courses is more or less than $CFG->courseswithsummarieslimit 1397 if ($totalcount <= $CFG->courseswithsummarieslimit) { 1398 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED); 1399 } else { 1400 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_COLLAPSED); 1401 } 1402 } 1403 1404 // prepare content of paging bar if it is needed 1405 $paginationurl = $chelper->get_courses_display_option('paginationurl'); 1406 $paginationallowall = $chelper->get_courses_display_option('paginationallowall'); 1407 if ($totalcount > count($courses)) { 1408 // there are more results that can fit on one page 1409 if ($paginationurl) { 1410 // the option paginationurl was specified, display pagingbar 1411 $perpage = $chelper->get_courses_display_option('limit', $CFG->coursesperpage); 1412 $page = $chelper->get_courses_display_option('offset') / $perpage; 1413 $pagingbar = $this->paging_bar($totalcount, $page, $perpage, 1414 $paginationurl->out(false, array('perpage' => $perpage))); 1415 if ($paginationallowall) { 1416 $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => 'all')), 1417 get_string('showall', '', $totalcount)), array('class' => 'paging paging-showall')); 1418 } 1419 } else if ($viewmoreurl = $chelper->get_courses_display_option('viewmoreurl')) { 1420 // the option for 'View more' link was specified, display more link 1421 $viewmoretext = $chelper->get_courses_display_option('viewmoretext', new lang_string('viewmore')); 1422 $morelink = html_writer::tag('div', html_writer::link($viewmoreurl, $viewmoretext), 1423 array('class' => 'paging paging-morelink')); 1424 } 1425 } else if (($totalcount > $CFG->coursesperpage) && $paginationurl && $paginationallowall) { 1426 // there are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode 1427 $pagingbar = html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => $CFG->coursesperpage)), 1428 get_string('showperpage', '', $CFG->coursesperpage)), array('class' => 'paging paging-showperpage')); 1429 } 1430 1431 // display list of courses 1432 $attributes = $chelper->get_and_erase_attributes('courses'); 1433 $content = html_writer::start_tag('div', $attributes); 1434 1435 if (!empty($pagingbar)) { 1436 $content .= $pagingbar; 1437 } 1438 1439 $coursecount = 0; 1440 foreach ($courses as $course) { 1441 $coursecount ++; 1442 $classes = ($coursecount%2) ? 'odd' : 'even'; 1443 if ($coursecount == 1) { 1444 $classes .= ' first'; 1445 } 1446 if ($coursecount >= count($courses)) { 1447 $classes .= ' last'; 1448 } 1449 $content .= $this->coursecat_coursebox($chelper, $course, $classes); 1450 } 1451 1452 if (!empty($pagingbar)) { 1453 $content .= $pagingbar; 1454 } 1455 if (!empty($morelink)) { 1456 $content .= $morelink; 1457 } 1458 1459 $content .= html_writer::end_tag('div'); // .courses 1460 return $content; 1461 } 1462 1463 /** 1464 * Renders the list of subcategories in a category 1465 * 1466 * @param coursecat_helper $chelper various display options 1467 * @param core_course_category $coursecat 1468 * @param int $depth depth of the category in the current tree 1469 * @return string 1470 */ 1471 protected function coursecat_subcategories(coursecat_helper $chelper, $coursecat, $depth) { 1472 global $CFG; 1473 $subcategories = array(); 1474 if (!$chelper->get_categories_display_option('nodisplay')) { 1475 $subcategories = $coursecat->get_children($chelper->get_categories_display_options()); 1476 } 1477 $totalcount = $coursecat->get_children_count(); 1478 if (!$totalcount) { 1479 // Note that we call core_course_category::get_children_count() AFTER core_course_category::get_children() 1480 // to avoid extra DB requests. 1481 // Categories count is cached during children categories retrieval. 1482 return ''; 1483 } 1484 1485 // prepare content of paging bar or more link if it is needed 1486 $paginationurl = $chelper->get_categories_display_option('paginationurl'); 1487 $paginationallowall = $chelper->get_categories_display_option('paginationallowall'); 1488 if ($totalcount > count($subcategories)) { 1489 if ($paginationurl) { 1490 // the option 'paginationurl was specified, display pagingbar 1491 $perpage = $chelper->get_categories_display_option('limit', $CFG->coursesperpage); 1492 $page = $chelper->get_categories_display_option('offset') / $perpage; 1493 $pagingbar = $this->paging_bar($totalcount, $page, $perpage, 1494 $paginationurl->out(false, array('perpage' => $perpage))); 1495 if ($paginationallowall) { 1496 $pagingbar .= html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => 'all')), 1497 get_string('showall', '', $totalcount)), array('class' => 'paging paging-showall')); 1498 } 1499 } else if ($viewmoreurl = $chelper->get_categories_display_option('viewmoreurl')) { 1500 // the option 'viewmoreurl' was specified, display more link (if it is link to category view page, add category id) 1501 if ($viewmoreurl->compare(new moodle_url('/course/index.php'), URL_MATCH_BASE)) { 1502 $viewmoreurl->param('categoryid', $coursecat->id); 1503 } 1504 $viewmoretext = $chelper->get_categories_display_option('viewmoretext', new lang_string('viewmore')); 1505 $morelink = html_writer::tag('div', html_writer::link($viewmoreurl, $viewmoretext), 1506 array('class' => 'paging paging-morelink')); 1507 } 1508 } else if (($totalcount > $CFG->coursesperpage) && $paginationurl && $paginationallowall) { 1509 // there are more than one page of results and we are in 'view all' mode, suggest to go back to paginated view mode 1510 $pagingbar = html_writer::tag('div', html_writer::link($paginationurl->out(false, array('perpage' => $CFG->coursesperpage)), 1511 get_string('showperpage', '', $CFG->coursesperpage)), array('class' => 'paging paging-showperpage')); 1512 } 1513 1514 // display list of subcategories 1515 $content = html_writer::start_tag('div', array('class' => 'subcategories')); 1516 1517 if (!empty($pagingbar)) { 1518 $content .= $pagingbar; 1519 } 1520 1521 foreach ($subcategories as $subcategory) { 1522 $content .= $this->coursecat_category($chelper, $subcategory, $depth + 1); 1523 } 1524 1525 if (!empty($pagingbar)) { 1526 $content .= $pagingbar; 1527 } 1528 if (!empty($morelink)) { 1529 $content .= $morelink; 1530 } 1531 1532 $content .= html_writer::end_tag('div'); 1533 return $content; 1534 } 1535 1536 /** 1537 * Make sure that javascript file for AJAX expanding of courses and categories content is included 1538 */ 1539 protected function coursecat_include_js() { 1540 if (!$this->page->requires->should_create_one_time_item_now('core_course_categoryexpanderjsinit')) { 1541 return; 1542 } 1543 1544 // We must only load this module once. 1545 $this->page->requires->yui_module('moodle-course-categoryexpander', 1546 'Y.Moodle.course.categoryexpander.init'); 1547 } 1548 1549 /** 1550 * Returns HTML to display the subcategories and courses in the given category 1551 * 1552 * This method is re-used by AJAX to expand content of not loaded category 1553 * 1554 * @param coursecat_helper $chelper various display options 1555 * @param core_course_category $coursecat 1556 * @param int $depth depth of the category in the current tree 1557 * @return string 1558 */ 1559 protected function coursecat_category_content(coursecat_helper $chelper, $coursecat, $depth) { 1560 $content = ''; 1561 // Subcategories 1562 $content .= $this->coursecat_subcategories($chelper, $coursecat, $depth); 1563 1564 // AUTO show courses: Courses will be shown expanded if this is not nested category, 1565 // and number of courses no bigger than $CFG->courseswithsummarieslimit. 1566 $showcoursesauto = $chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_AUTO; 1567 if ($showcoursesauto && $depth) { 1568 // this is definitely collapsed mode 1569 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_COLLAPSED); 1570 } 1571 1572 // Courses 1573 if ($chelper->get_show_courses() > core_course_renderer::COURSECAT_SHOW_COURSES_COUNT) { 1574 $courses = array(); 1575 if (!$chelper->get_courses_display_option('nodisplay')) { 1576 $courses = $coursecat->get_courses($chelper->get_courses_display_options()); 1577 } 1578 if ($viewmoreurl = $chelper->get_courses_display_option('viewmoreurl')) { 1579 // the option for 'View more' link was specified, display more link (if it is link to category view page, add category id) 1580 if ($viewmoreurl->compare(new moodle_url('/course/index.php'), URL_MATCH_BASE)) { 1581 $chelper->set_courses_display_option('viewmoreurl', new moodle_url($viewmoreurl, array('categoryid' => $coursecat->id))); 1582 } 1583 } 1584 $content .= $this->coursecat_courses($chelper, $courses, $coursecat->get_courses_count()); 1585 } 1586 1587 if ($showcoursesauto) { 1588 // restore the show_courses back to AUTO 1589 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_AUTO); 1590 } 1591 1592 return $content; 1593 } 1594 1595 /** 1596 * Returns HTML to display a course category as a part of a tree 1597 * 1598 * This is an internal function, to display a particular category and all its contents 1599 * use {@link core_course_renderer::course_category()} 1600 * 1601 * @param coursecat_helper $chelper various display options 1602 * @param core_course_category $coursecat 1603 * @param int $depth depth of this category in the current tree 1604 * @return string 1605 */ 1606 protected function coursecat_category(coursecat_helper $chelper, $coursecat, $depth) { 1607 // open category tag 1608 $classes = array('category'); 1609 if (empty($coursecat->visible)) { 1610 $classes[] = 'dimmed_category'; 1611 } 1612 if ($chelper->get_subcat_depth() > 0 && $depth >= $chelper->get_subcat_depth()) { 1613 // do not load content 1614 $categorycontent = ''; 1615 $classes[] = 'notloaded'; 1616 if ($coursecat->get_children_count() || 1617 ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_COLLAPSED && $coursecat->get_courses_count())) { 1618 $classes[] = 'with_children'; 1619 $classes[] = 'collapsed'; 1620 } 1621 } else { 1622 // load category content 1623 $categorycontent = $this->coursecat_category_content($chelper, $coursecat, $depth); 1624 $classes[] = 'loaded'; 1625 if (!empty($categorycontent)) { 1626 $classes[] = 'with_children'; 1627 // Category content loaded with children. 1628 $this->categoryexpandedonload = true; 1629 } 1630 } 1631 1632 // Make sure JS file to expand category content is included. 1633 $this->coursecat_include_js(); 1634 1635 $content = html_writer::start_tag('div', array( 1636 'class' => join(' ', $classes), 1637 'data-categoryid' => $coursecat->id, 1638 'data-depth' => $depth, 1639 'data-showcourses' => $chelper->get_show_courses(), 1640 'data-type' => self::COURSECAT_TYPE_CATEGORY, 1641 )); 1642 1643 // category name 1644 $categoryname = $coursecat->get_formatted_name(); 1645 $categoryname = html_writer::link(new moodle_url('/course/index.php', 1646 array('categoryid' => $coursecat->id)), 1647 $categoryname); 1648 if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_COUNT 1649 && ($coursescount = $coursecat->get_courses_count())) { 1650 $categoryname .= html_writer::tag('span', ' ('. $coursescount.')', 1651 array('title' => get_string('numberofcourses'), 'class' => 'numberofcourse')); 1652 } 1653 $content .= html_writer::start_tag('div', array('class' => 'info')); 1654 1655 $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname aabtn')); 1656 $content .= html_writer::end_tag('div'); // .info 1657 1658 // add category content to the output 1659 $content .= html_writer::tag('div', $categorycontent, array('class' => 'content')); 1660 1661 $content .= html_writer::end_tag('div'); // .category 1662 1663 // Return the course category tree HTML 1664 return $content; 1665 } 1666 1667 /** 1668 * Returns HTML to display a tree of subcategories and courses in the given category 1669 * 1670 * @param coursecat_helper $chelper various display options 1671 * @param core_course_category $coursecat top category (this category's name and description will NOT be added to the tree) 1672 * @return string 1673 */ 1674 protected function coursecat_tree(coursecat_helper $chelper, $coursecat) { 1675 // Reset the category expanded flag for this course category tree first. 1676 $this->categoryexpandedonload = false; 1677 $categorycontent = $this->coursecat_category_content($chelper, $coursecat, 0); 1678 if (empty($categorycontent)) { 1679 return ''; 1680 } 1681 1682 // Start content generation 1683 $content = ''; 1684 $attributes = $chelper->get_and_erase_attributes('course_category_tree clearfix'); 1685 $content .= html_writer::start_tag('div', $attributes); 1686 1687 if ($coursecat->get_children_count()) { 1688 $classes = array( 1689 'collapseexpand', 'aabtn' 1690 ); 1691 1692 // Check if the category content contains subcategories with children's content loaded. 1693 if ($this->categoryexpandedonload) { 1694 $classes[] = 'collapse-all'; 1695 $linkname = get_string('collapseall'); 1696 } else { 1697 $linkname = get_string('expandall'); 1698 } 1699 1700 // Only show the collapse/expand if there are children to expand. 1701 $content .= html_writer::start_tag('div', array('class' => 'collapsible-actions')); 1702 $content .= html_writer::link('#', $linkname, array('class' => implode(' ', $classes), 'aria-expanded' => false)); 1703 $content .= html_writer::end_tag('div'); 1704 $this->page->requires->strings_for_js(array('collapseall', 'expandall'), 'moodle'); 1705 } 1706 1707 $content .= html_writer::tag('div', $categorycontent, array('class' => 'content')); 1708 1709 $content .= html_writer::end_tag('div'); // .course_category_tree 1710 1711 return $content; 1712 } 1713 1714 /** 1715 * Renders HTML to display particular course category - list of it's subcategories and courses 1716 * 1717 * Invoked from /course/index.php 1718 * 1719 * @param int|stdClass|core_course_category $category 1720 */ 1721 public function course_category($category) { 1722 global $CFG; 1723 $usertop = core_course_category::user_top(); 1724 if (empty($category)) { 1725 $coursecat = $usertop; 1726 } else if (is_object($category) && $category instanceof core_course_category) { 1727 $coursecat = $category; 1728 } else { 1729 $coursecat = core_course_category::get(is_object($category) ? $category->id : $category); 1730 } 1731 $site = get_site(); 1732 $output = ''; 1733 1734 if ($coursecat->can_create_course() || $coursecat->has_manage_capability()) { 1735 // Add 'Manage' button if user has permissions to edit this category. 1736 $managebutton = $this->single_button(new moodle_url('/course/management.php', 1737 array('categoryid' => $coursecat->id)), get_string('managecourses'), 'get'); 1738 $this->page->set_button($managebutton); 1739 } 1740 1741 if (core_course_category::is_simple_site()) { 1742 // There is only one category in the system, do not display link to it. 1743 $strfulllistofcourses = get_string('fulllistofcourses'); 1744 $this->page->set_title("$site->shortname: $strfulllistofcourses"); 1745 } else if (!$coursecat->id || !$coursecat->is_uservisible()) { 1746 $strcategories = get_string('categories'); 1747 $this->page->set_title("$site->shortname: $strcategories"); 1748 } else { 1749 $strfulllistofcourses = get_string('fulllistofcourses'); 1750 $this->page->set_title("$site->shortname: $strfulllistofcourses"); 1751 1752 // Print the category selector 1753 $categorieslist = core_course_category::make_categories_list(); 1754 if (count($categorieslist) > 1) { 1755 $output .= html_writer::start_tag('div', array('class' => 'categorypicker')); 1756 $select = new single_select(new moodle_url('/course/index.php'), 'categoryid', 1757 core_course_category::make_categories_list(), $coursecat->id, null, 'switchcategory'); 1758 $select->set_label(get_string('categories').':'); 1759 $output .= $this->render($select); 1760 $output .= html_writer::end_tag('div'); // .categorypicker 1761 } 1762 } 1763 1764 // Print current category description 1765 $chelper = new coursecat_helper(); 1766 if ($description = $chelper->get_category_formatted_description($coursecat)) { 1767 $output .= $this->box($description, array('class' => 'generalbox info')); 1768 } 1769 1770 // Prepare parameters for courses and categories lists in the tree 1771 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_AUTO) 1772 ->set_attributes(array('class' => 'category-browse category-browse-'.$coursecat->id)); 1773 1774 $coursedisplayoptions = array(); 1775 $catdisplayoptions = array(); 1776 $browse = optional_param('browse', null, PARAM_ALPHA); 1777 $perpage = optional_param('perpage', $CFG->coursesperpage, PARAM_INT); 1778 $page = optional_param('page', 0, PARAM_INT); 1779 $baseurl = new moodle_url('/course/index.php'); 1780 if ($coursecat->id) { 1781 $baseurl->param('categoryid', $coursecat->id); 1782 } 1783 if ($perpage != $CFG->coursesperpage) { 1784 $baseurl->param('perpage', $perpage); 1785 } 1786 $coursedisplayoptions['limit'] = $perpage; 1787 $catdisplayoptions['limit'] = $perpage; 1788 if ($browse === 'courses' || !$coursecat->get_children_count()) { 1789 $coursedisplayoptions['offset'] = $page * $perpage; 1790 $coursedisplayoptions['paginationurl'] = new moodle_url($baseurl, array('browse' => 'courses')); 1791 $catdisplayoptions['nodisplay'] = true; 1792 $catdisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'categories')); 1793 $catdisplayoptions['viewmoretext'] = new lang_string('viewallsubcategories'); 1794 } else if ($browse === 'categories' || !$coursecat->get_courses_count()) { 1795 $coursedisplayoptions['nodisplay'] = true; 1796 $catdisplayoptions['offset'] = $page * $perpage; 1797 $catdisplayoptions['paginationurl'] = new moodle_url($baseurl, array('browse' => 'categories')); 1798 $coursedisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'courses')); 1799 $coursedisplayoptions['viewmoretext'] = new lang_string('viewallcourses'); 1800 } else { 1801 // we have a category that has both subcategories and courses, display pagination separately 1802 $coursedisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'courses', 'page' => 1)); 1803 $catdisplayoptions['viewmoreurl'] = new moodle_url($baseurl, array('browse' => 'categories', 'page' => 1)); 1804 } 1805 $chelper->set_courses_display_options($coursedisplayoptions)->set_categories_display_options($catdisplayoptions); 1806 // Add course search form. 1807 $output .= $this->course_search_form(); 1808 1809 // Display course category tree. 1810 $output .= $this->coursecat_tree($chelper, $coursecat); 1811 1812 // Add action buttons 1813 $output .= $this->container_start('buttons mt-3'); 1814 if ($coursecat->is_uservisible()) { 1815 $context = get_category_or_system_context($coursecat->id); 1816 if (has_capability('moodle/course:create', $context)) { 1817 // Print link to create a new course, for the 1st available category. 1818 if ($coursecat->id) { 1819 $url = new moodle_url('/course/edit.php', array('category' => $coursecat->id, 'returnto' => 'category')); 1820 } else { 1821 $url = new moodle_url('/course/edit.php', 1822 array('category' => $CFG->defaultrequestcategory, 'returnto' => 'topcat')); 1823 } 1824 $output .= $this->single_button($url, get_string('addnewcourse'), 'get'); 1825 } 1826 ob_start(); 1827 print_course_request_buttons($context); 1828 $output .= ob_get_contents(); 1829 ob_end_clean(); 1830 } 1831 $output .= $this->container_end(); 1832 1833 return $output; 1834 } 1835 1836 /** 1837 * Serves requests to /course/category.ajax.php 1838 * 1839 * In this renderer implementation it may expand the category content or 1840 * course content. 1841 * 1842 * @return string 1843 * @throws coding_exception 1844 */ 1845 public function coursecat_ajax() { 1846 global $DB, $CFG; 1847 1848 $type = required_param('type', PARAM_INT); 1849 1850 if ($type === self::COURSECAT_TYPE_CATEGORY) { 1851 // This is a request for a category list of some kind. 1852 $categoryid = required_param('categoryid', PARAM_INT); 1853 $showcourses = required_param('showcourses', PARAM_INT); 1854 $depth = required_param('depth', PARAM_INT); 1855 1856 $category = core_course_category::get($categoryid); 1857 1858 $chelper = new coursecat_helper(); 1859 $baseurl = new moodle_url('/course/index.php', array('categoryid' => $categoryid)); 1860 $coursedisplayoptions = array( 1861 'limit' => $CFG->coursesperpage, 1862 'viewmoreurl' => new moodle_url($baseurl, array('browse' => 'courses', 'page' => 1)) 1863 ); 1864 $catdisplayoptions = array( 1865 'limit' => $CFG->coursesperpage, 1866 'viewmoreurl' => new moodle_url($baseurl, array('browse' => 'categories', 'page' => 1)) 1867 ); 1868 $chelper->set_show_courses($showcourses)-> 1869 set_courses_display_options($coursedisplayoptions)-> 1870 set_categories_display_options($catdisplayoptions); 1871 1872 return $this->coursecat_category_content($chelper, $category, $depth); 1873 } else if ($type === self::COURSECAT_TYPE_COURSE) { 1874 // This is a request for the course information. 1875 $courseid = required_param('courseid', PARAM_INT); 1876 1877 $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); 1878 1879 $chelper = new coursecat_helper(); 1880 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED); 1881 return $this->coursecat_coursebox_content($chelper, $course); 1882 } else { 1883 throw new coding_exception('Invalid request type'); 1884 } 1885 } 1886 1887 /** 1888 * Renders html to display search result page 1889 * 1890 * @param array $searchcriteria may contain elements: search, blocklist, modulelist, tagid 1891 * @return string 1892 */ 1893 public function search_courses($searchcriteria) { 1894 global $CFG; 1895 $content = ''; 1896 1897 $search = ''; 1898 if (!empty($searchcriteria['search'])) { 1899 $search = $searchcriteria['search']; 1900 } 1901 $content .= $this->course_search_form($search); 1902 1903 if (!empty($searchcriteria)) { 1904 // print search results 1905 1906 $displayoptions = array('sort' => array('displayname' => 1)); 1907 // take the current page and number of results per page from query 1908 $perpage = optional_param('perpage', 0, PARAM_RAW); 1909 if ($perpage !== 'all') { 1910 $displayoptions['limit'] = ((int)$perpage <= 0) ? $CFG->coursesperpage : (int)$perpage; 1911 $page = optional_param('page', 0, PARAM_INT); 1912 $displayoptions['offset'] = $displayoptions['limit'] * $page; 1913 } 1914 // options 'paginationurl' and 'paginationallowall' are only used in method coursecat_courses() 1915 $displayoptions['paginationurl'] = new moodle_url('/course/search.php', $searchcriteria); 1916 $displayoptions['paginationallowall'] = true; // allow adding link 'View all' 1917 1918 $class = 'course-search-result'; 1919 foreach ($searchcriteria as $key => $value) { 1920 if (!empty($value)) { 1921 $class .= ' course-search-result-'. $key; 1922 } 1923 } 1924 $chelper = new coursecat_helper(); 1925 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT)-> 1926 set_courses_display_options($displayoptions)-> 1927 set_search_criteria($searchcriteria)-> 1928 set_attributes(array('class' => $class)); 1929 1930 $courses = core_course_category::search_courses($searchcriteria, $chelper->get_courses_display_options()); 1931 $totalcount = core_course_category::search_courses_count($searchcriteria); 1932 $courseslist = $this->coursecat_courses($chelper, $courses, $totalcount); 1933 1934 if (!$totalcount) { 1935 if (!empty($searchcriteria['search'])) { 1936 $content .= $this->heading(get_string('nocoursesfound', '', $searchcriteria['search'])); 1937 } else { 1938 $content .= $this->heading(get_string('novalidcourses')); 1939 } 1940 } else { 1941 $content .= $this->heading(get_string('searchresults'). ": $totalcount"); 1942 $content .= $courseslist; 1943 } 1944 } 1945 return $content; 1946 } 1947 1948 /** 1949 * Renders html to print list of courses tagged with particular tag 1950 * 1951 * @param int $tagid id of the tag 1952 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 1953 * are displayed on the page and the per-page limit may be bigger 1954 * @param int $fromctx context id where the link was displayed, may be used by callbacks 1955 * to display items in the same context first 1956 * @param int $ctx context id where to search for records 1957 * @param bool $rec search in subcontexts as well 1958 * @param array $displayoptions 1959 * @return string empty string if no courses are marked with this tag or rendered list of courses 1960 */ 1961 public function tagged_courses($tagid, $exclusivemode = true, $ctx = 0, $rec = true, $displayoptions = null) { 1962 global $CFG; 1963 if (empty($displayoptions)) { 1964 $displayoptions = array(); 1965 } 1966 $showcategories = !core_course_category::is_simple_site(); 1967 $displayoptions += array('limit' => $CFG->coursesperpage, 'offset' => 0); 1968 $chelper = new coursecat_helper(); 1969 $searchcriteria = array('tagid' => $tagid, 'ctx' => $ctx, 'rec' => $rec); 1970 $chelper->set_show_courses($showcategories ? self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT : 1971 self::COURSECAT_SHOW_COURSES_EXPANDED)-> 1972 set_search_criteria($searchcriteria)-> 1973 set_courses_display_options($displayoptions)-> 1974 set_attributes(array('class' => 'course-search-result course-search-result-tagid')); 1975 // (we set the same css class as in search results by tagid) 1976 if ($totalcount = core_course_category::search_courses_count($searchcriteria)) { 1977 $courses = core_course_category::search_courses($searchcriteria, $chelper->get_courses_display_options()); 1978 if ($exclusivemode) { 1979 return $this->coursecat_courses($chelper, $courses, $totalcount); 1980 } else { 1981 $tagfeed = new core_tag\output\tagfeed(); 1982 $img = $this->output->pix_icon('i/course', ''); 1983 foreach ($courses as $course) { 1984 $url = course_get_url($course); 1985 $imgwithlink = html_writer::link($url, $img); 1986 $coursename = html_writer::link($url, $course->get_formatted_name()); 1987 $details = ''; 1988 if ($showcategories && ($cat = core_course_category::get($course->category, IGNORE_MISSING))) { 1989 $details = get_string('category').': '. 1990 html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)), 1991 $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed')); 1992 } 1993 $tagfeed->add($imgwithlink, $coursename, $details); 1994 } 1995 return $this->output->render_from_template('core_tag/tagfeed', $tagfeed->export_for_template($this->output)); 1996 } 1997 } 1998 return ''; 1999 } 2000 2001 /** 2002 * Returns HTML to display one remote course 2003 * 2004 * @param stdClass $course remote course information, contains properties: 2005 id, remoteid, shortname, fullname, hostid, summary, summaryformat, cat_name, hostname 2006 * @return string 2007 */ 2008 protected function frontpage_remote_course(stdClass $course) { 2009 $url = new moodle_url('/auth/mnet/jump.php', array( 2010 'hostid' => $course->hostid, 2011 'wantsurl' => '/course/view.php?id='. $course->remoteid 2012 )); 2013 2014 $output = ''; 2015 $output .= html_writer::start_tag('div', array('class' => 'coursebox remotecoursebox clearfix')); 2016 $output .= html_writer::start_tag('div', array('class' => 'info')); 2017 $output .= html_writer::start_tag('h3', array('class' => 'name')); 2018 $output .= html_writer::link($url, format_string($course->fullname), array('title' => get_string('entercourse'))); 2019 $output .= html_writer::end_tag('h3'); // .name 2020 $output .= html_writer::tag('div', '', array('class' => 'moreinfo')); 2021 $output .= html_writer::end_tag('div'); // .info 2022 $output .= html_writer::start_tag('div', array('class' => 'content')); 2023 $output .= html_writer::start_tag('div', array('class' => 'summary')); 2024 $options = new stdClass(); 2025 $options->noclean = true; 2026 $options->para = false; 2027 $options->overflowdiv = true; 2028 $output .= format_text($course->summary, $course->summaryformat, $options); 2029 $output .= html_writer::end_tag('div'); // .summary 2030 $addinfo = format_string($course->hostname) . ' : ' 2031 . format_string($course->cat_name) . ' : ' 2032 . format_string($course->shortname); 2033 $output .= html_writer::tag('div', $addinfo, array('class' => 'remotecourseinfo')); 2034 $output .= html_writer::end_tag('div'); // .content 2035 $output .= html_writer::end_tag('div'); // .coursebox 2036 return $output; 2037 } 2038 2039 /** 2040 * Returns HTML to display one remote host 2041 * 2042 * @param array $host host information, contains properties: name, url, count 2043 * @return string 2044 */ 2045 protected function frontpage_remote_host($host) { 2046 $output = ''; 2047 $output .= html_writer::start_tag('div', array('class' => 'coursebox remotehost clearfix')); 2048 $output .= html_writer::start_tag('div', array('class' => 'info')); 2049 $output .= html_writer::start_tag('h3', array('class' => 'name')); 2050 $output .= html_writer::link($host['url'], s($host['name']), array('title' => s($host['name']))); 2051 $output .= html_writer::end_tag('h3'); // .name 2052 $output .= html_writer::tag('div', '', array('class' => 'moreinfo')); 2053 $output .= html_writer::end_tag('div'); // .info 2054 $output .= html_writer::start_tag('div', array('class' => 'content')); 2055 $output .= html_writer::start_tag('div', array('class' => 'summary')); 2056 $output .= $host['count'] . ' ' . get_string('courses'); 2057 $output .= html_writer::end_tag('div'); // .content 2058 $output .= html_writer::end_tag('div'); // .coursebox 2059 return $output; 2060 } 2061 2062 /** 2063 * Returns HTML to print list of courses user is enrolled to for the frontpage 2064 * 2065 * Also lists remote courses or remote hosts if MNET authorisation is used 2066 * 2067 * @return string 2068 */ 2069 public function frontpage_my_courses() { 2070 global $USER, $CFG, $DB; 2071 2072 if (!isloggedin() or isguestuser()) { 2073 return ''; 2074 } 2075 2076 $output = ''; 2077 $courses = enrol_get_my_courses('summary, summaryformat'); 2078 $rhosts = array(); 2079 $rcourses = array(); 2080 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') { 2081 $rcourses = get_my_remotecourses($USER->id); 2082 $rhosts = get_my_remotehosts(); 2083 } 2084 2085 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) { 2086 2087 $chelper = new coursecat_helper(); 2088 $totalcount = count($courses); 2089 if (count($courses) > $CFG->frontpagecourselimit) { 2090 // There are more enrolled courses than we can display, display link to 'My courses'. 2091 $courses = array_slice($courses, 0, $CFG->frontpagecourselimit, true); 2092 $chelper->set_courses_display_options(array( 2093 'viewmoreurl' => new moodle_url('/my/'), 2094 'viewmoretext' => new lang_string('mycourses') 2095 )); 2096 } else if (core_course_category::top()->is_uservisible()) { 2097 // All enrolled courses are displayed, display link to 'All courses' if there are more courses in system. 2098 $chelper->set_courses_display_options(array( 2099 'viewmoreurl' => new moodle_url('/course/index.php'), 2100 'viewmoretext' => new lang_string('fulllistofcourses') 2101 )); 2102 $totalcount = $DB->count_records('course') - 1; 2103 } 2104 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED)-> 2105 set_attributes(array('class' => 'frontpage-course-list-enrolled')); 2106 $output .= $this->coursecat_courses($chelper, $courses, $totalcount); 2107 2108 // MNET 2109 if (!empty($rcourses)) { 2110 // at the IDP, we know of all the remote courses 2111 $output .= html_writer::start_tag('div', array('class' => 'courses')); 2112 foreach ($rcourses as $course) { 2113 $output .= $this->frontpage_remote_course($course); 2114 } 2115 $output .= html_writer::end_tag('div'); // .courses 2116 } elseif (!empty($rhosts)) { 2117 // non-IDP, we know of all the remote servers, but not courses 2118 $output .= html_writer::start_tag('div', array('class' => 'courses')); 2119 foreach ($rhosts as $host) { 2120 $output .= $this->frontpage_remote_host($host); 2121 } 2122 $output .= html_writer::end_tag('div'); // .courses 2123 } 2124 } 2125 return $output; 2126 } 2127 2128 /** 2129 * Returns HTML to print list of available courses for the frontpage 2130 * 2131 * @return string 2132 */ 2133 public function frontpage_available_courses() { 2134 global $CFG; 2135 2136 $chelper = new coursecat_helper(); 2137 $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED)-> 2138 set_courses_display_options(array( 2139 'recursive' => true, 2140 'limit' => $CFG->frontpagecourselimit, 2141 'viewmoreurl' => new moodle_url('/course/index.php'), 2142 'viewmoretext' => new lang_string('fulllistofcourses'))); 2143 2144 $chelper->set_attributes(array('class' => 'frontpage-course-list-all')); 2145 $courses = core_course_category::top()->get_courses($chelper->get_courses_display_options()); 2146 $totalcount = core_course_category::top()->get_courses_count($chelper->get_courses_display_options()); 2147 if (!$totalcount && !$this->page->user_is_editing() && has_capability('moodle/course:create', context_system::instance())) { 2148 // Print link to create a new course, for the 1st available category. 2149 return $this->add_new_course_button(); 2150 } 2151 return $this->coursecat_courses($chelper, $courses, $totalcount); 2152 } 2153 2154 /** 2155 * Returns HTML to the "add new course" button for the page 2156 * 2157 * @return string 2158 */ 2159 public function add_new_course_button() { 2160 global $CFG; 2161 // Print link to create a new course, for the 1st available category. 2162 $output = $this->container_start('buttons'); 2163 $url = new moodle_url('/course/edit.php', array('category' => $CFG->defaultrequestcategory, 'returnto' => 'topcat')); 2164 $output .= $this->single_button($url, get_string('addnewcourse'), 'get'); 2165 $output .= $this->container_end('buttons'); 2166 return $output; 2167 } 2168 2169 /** 2170 * Returns HTML to print tree with course categories and courses for the frontpage 2171 * 2172 * @return string 2173 */ 2174 public function frontpage_combo_list() { 2175 global $CFG; 2176 // TODO MDL-10965 improve. 2177 $tree = core_course_category::top(); 2178 if (!$tree->get_children_count()) { 2179 return ''; 2180 } 2181 $chelper = new coursecat_helper(); 2182 $chelper->set_subcat_depth($CFG->maxcategorydepth)-> 2183 set_categories_display_options(array( 2184 'limit' => $CFG->coursesperpage, 2185 'viewmoreurl' => new moodle_url('/course/index.php', 2186 array('browse' => 'categories', 'page' => 1)) 2187 ))-> 2188 set_courses_display_options(array( 2189 'limit' => $CFG->coursesperpage, 2190 'viewmoreurl' => new moodle_url('/course/index.php', 2191 array('browse' => 'courses', 'page' => 1)) 2192 ))-> 2193 set_attributes(array('class' => 'frontpage-category-combo')); 2194 return $this->coursecat_tree($chelper, $tree); 2195 } 2196 2197 /** 2198 * Returns HTML to print tree of course categories (with number of courses) for the frontpage 2199 * 2200 * @return string 2201 */ 2202 public function frontpage_categories_list() { 2203 global $CFG; 2204 // TODO MDL-10965 improve. 2205 $tree = core_course_category::top(); 2206 if (!$tree->get_children_count()) { 2207 return ''; 2208 } 2209 $chelper = new coursecat_helper(); 2210 $chelper->set_subcat_depth($CFG->maxcategorydepth)-> 2211 set_show_courses(self::COURSECAT_SHOW_COURSES_COUNT)-> 2212 set_categories_display_options(array( 2213 'limit' => $CFG->coursesperpage, 2214 'viewmoreurl' => new moodle_url('/course/index.php', 2215 array('browse' => 'categories', 'page' => 1)) 2216 ))-> 2217 set_attributes(array('class' => 'frontpage-category-names')); 2218 return $this->coursecat_tree($chelper, $tree); 2219 } 2220 2221 /** 2222 * Renders the activity information. 2223 * 2224 * Defer to template. 2225 * 2226 * @param \core_course\output\activity_information $page 2227 * @return string html for the page 2228 */ 2229 public function render_activity_information(\core_course\output\activity_information $page) { 2230 $data = $page->export_for_template($this->output); 2231 return $this->output->render_from_template('core_course/activity_info', $data); 2232 } 2233 2234 /** 2235 * Renders the activity navigation. 2236 * 2237 * Defer to template. 2238 * 2239 * @param \core_course\output\activity_navigation $page 2240 * @return string html for the page 2241 */ 2242 public function render_activity_navigation(\core_course\output\activity_navigation $page) { 2243 $data = $page->export_for_template($this->output); 2244 return $this->output->render_from_template('core_course/activity_navigation', $data); 2245 } 2246 2247 /** 2248 * Display waiting information about backup size during uploading backup process 2249 * @param object $backupfile the backup stored_file 2250 * @return $html string 2251 */ 2252 public function sendingbackupinfo($backupfile) { 2253 $sizeinfo = new stdClass(); 2254 $sizeinfo->total = number_format($backupfile->get_filesize() / 1000000, 2); 2255 $html = html_writer::tag('div', get_string('sendingsize', 'hub', $sizeinfo), 2256 array('class' => 'courseuploadtextinfo')); 2257 return $html; 2258 } 2259 2260 /** 2261 * Hub information (logo - name - description - link) 2262 * @param object $hubinfo 2263 * @return string html code 2264 */ 2265 public function hubinfo($hubinfo) { 2266 $screenshothtml = html_writer::empty_tag('img', 2267 array('src' => $hubinfo['imgurl'], 'alt' => $hubinfo['name'])); 2268 $hubdescription = html_writer::tag('div', $screenshothtml, 2269 array('class' => 'hubscreenshot')); 2270 2271 $hubdescription .= html_writer::tag('a', $hubinfo['name'], 2272 array('class' => 'hublink', 'href' => $hubinfo['url'], 2273 'onclick' => 'this.target="_blank"')); 2274 2275 $hubdescription .= html_writer::tag('div', format_text($hubinfo['description'], FORMAT_PLAIN), 2276 array('class' => 'hubdescription')); 2277 $hubdescription = html_writer::tag('div', $hubdescription, array('class' => 'hubinfo clearfix')); 2278 2279 return $hubdescription; 2280 } 2281 2282 /** 2283 * Output frontpage summary text and frontpage modules (stored as section 1 in site course) 2284 * 2285 * This may be disabled in settings 2286 * 2287 * @return string 2288 */ 2289 public function frontpage_section1() { 2290 global $SITE, $USER; 2291 2292 $output = ''; 2293 $editing = $this->page->user_is_editing(); 2294 2295 if ($editing) { 2296 // Make sure section with number 1 exists. 2297 course_create_sections_if_missing($SITE, 1); 2298 } 2299 2300 $modinfo = get_fast_modinfo($SITE); 2301 $section = $modinfo->get_section_info(1); 2302 if (($section && (!empty($modinfo->sections[1]) or !empty($section->summary))) or $editing) { 2303 $output .= $this->box_start('generalbox sitetopic'); 2304 2305 // If currently moving a file then show the current clipboard. 2306 if (ismoving($SITE->id)) { 2307 $stractivityclipboard = strip_tags(get_string('activityclipboard', '', $USER->activitycopyname)); 2308 $output .= '<p><font size="2">'; 2309 $cancelcopyurl = new moodle_url('/course/mod.php', ['cancelcopy' => 'true', 'sesskey' => sesskey()]); 2310 $output .= "$stractivityclipboard (" . html_writer::link($cancelcopyurl, get_string('cancel')) .')'; 2311 $output .= '</font></p>'; 2312 } 2313 2314 $context = context_course::instance(SITEID); 2315 2316 // If the section name is set we show it. 2317 if (trim($section->name) !== '') { 2318 $output .= $this->heading( 2319 format_string($section->name, true, array('context' => $context)), 2320 2, 2321 'sectionname' 2322 ); 2323 } 2324 2325 $summarytext = file_rewrite_pluginfile_urls($section->summary, 2326 'pluginfile.php', 2327 $context->id, 2328 'course', 2329 'section', 2330 $section->id); 2331 $summaryformatoptions = new stdClass(); 2332 $summaryformatoptions->noclean = true; 2333 $summaryformatoptions->overflowdiv = true; 2334 2335 $output .= format_text($summarytext, $section->summaryformat, $summaryformatoptions); 2336 2337 if ($editing && has_capability('moodle/course:update', $context)) { 2338 $streditsummary = get_string('editsummary'); 2339 $editsectionurl = new moodle_url('/course/editsection.php', ['id' => $section->id]); 2340 $attributes = ['title' => $streditsummary, 'aria-label' => $streditsummary]; 2341 $output .= html_writer::link($editsectionurl, $this->pix_icon('t/edit', ''), $attributes) . 2342 "<br /><br />"; 2343 } 2344 2345 $output .= $this->course_section_cm_list($SITE, $section); 2346 2347 $output .= $this->course_section_add_cm_control($SITE, $section->section); 2348 $output .= $this->box_end(); 2349 } 2350 2351 return $output; 2352 } 2353 2354 /** 2355 * Output news for the frontpage (extract from site-wide news forum) 2356 * 2357 * @param stdClass $forum record from db table 'forum' that represents the site news forum 2358 * @return string 2359 */ 2360 protected function frontpage_news($forum) { 2361 global $CFG, $SITE, $SESSION, $USER; 2362 require_once($CFG->dirroot .'/mod/forum/lib.php'); 2363 2364 $output = ''; 2365 2366 if (isloggedin()) { 2367 $SESSION->fromdiscussion = $CFG->wwwroot; 2368 $subtext = ''; 2369 if (\mod_forum\subscriptions::is_subscribed($USER->id, $forum)) { 2370 if (!\mod_forum\subscriptions::is_forcesubscribed($forum)) { 2371 $subtext = get_string('unsubscribe', 'forum'); 2372 } 2373 } else { 2374 $subtext = get_string('subscribe', 'forum'); 2375 } 2376 $suburl = new moodle_url('/mod/forum/subscribe.php', array('id' => $forum->id, 'sesskey' => sesskey())); 2377 $output .= html_writer::tag('div', html_writer::link($suburl, $subtext), array('class' => 'subscribelink')); 2378 } 2379 2380 $coursemodule = get_coursemodule_from_instance('forum', $forum->id); 2381 $context = context_module::instance($coursemodule->id); 2382 2383 $entityfactory = mod_forum\local\container::get_entity_factory(); 2384 $forumentity = $entityfactory->get_forum_from_stdclass($forum, $context, $coursemodule, $SITE); 2385 2386 $rendererfactory = mod_forum\local\container::get_renderer_factory(); 2387 $discussionsrenderer = $rendererfactory->get_frontpage_news_discussion_list_renderer($forumentity); 2388 $cm = \cm_info::create($coursemodule); 2389 return $output . $discussionsrenderer->render($USER, $cm, null, null, 0, $SITE->newsitems); 2390 } 2391 2392 /** 2393 * Renders part of frontpage with a skip link (i.e. "My courses", "Site news", etc.) 2394 * 2395 * @param string $skipdivid 2396 * @param string $contentsdivid 2397 * @param string $header Header of the part 2398 * @param string $contents Contents of the part 2399 * @return string 2400 */ 2401 protected function frontpage_part($skipdivid, $contentsdivid, $header, $contents) { 2402 if (strval($contents) === '') { 2403 return ''; 2404 } 2405 $output = html_writer::link('#' . $skipdivid, 2406 get_string('skipa', 'access', core_text::strtolower(strip_tags($header))), 2407 array('class' => 'skip-block skip aabtn')); 2408 2409 // Wrap frontpage part in div container. 2410 $output .= html_writer::start_tag('div', array('id' => $contentsdivid)); 2411 $output .= $this->heading($header); 2412 2413 $output .= $contents; 2414 2415 // End frontpage part div container. 2416 $output .= html_writer::end_tag('div'); 2417 2418 $output .= html_writer::tag('span', '', array('class' => 'skip-block-to', 'id' => $skipdivid)); 2419 return $output; 2420 } 2421 2422 /** 2423 * Outputs contents for frontpage as configured in $CFG->frontpage or $CFG->frontpageloggedin 2424 * 2425 * @return string 2426 */ 2427 public function frontpage() { 2428 global $CFG, $SITE; 2429 2430 $output = ''; 2431 2432 if (isloggedin() and !isguestuser() and isset($CFG->frontpageloggedin)) { 2433 $frontpagelayout = $CFG->frontpageloggedin; 2434 } else { 2435 $frontpagelayout = $CFG->frontpage; 2436 } 2437 2438 foreach (explode(',', $frontpagelayout) as $v) { 2439 switch ($v) { 2440 // Display the main part of the front page. 2441 case FRONTPAGENEWS: 2442 if ($SITE->newsitems) { 2443 // Print forums only when needed. 2444 require_once($CFG->dirroot .'/mod/forum/lib.php'); 2445 if (($newsforum = forum_get_course_forum($SITE->id, 'news')) && 2446 ($forumcontents = $this->frontpage_news($newsforum))) { 2447 $newsforumcm = get_fast_modinfo($SITE)->instances['forum'][$newsforum->id]; 2448 $output .= $this->frontpage_part('skipsitenews', 'site-news-forum', 2449 $newsforumcm->get_formatted_name(), $forumcontents); 2450 } 2451 } 2452 break; 2453 2454 case FRONTPAGEENROLLEDCOURSELIST: 2455 $mycourseshtml = $this->frontpage_my_courses(); 2456 if (!empty($mycourseshtml)) { 2457 $output .= $this->frontpage_part('skipmycourses', 'frontpage-course-list', 2458 get_string('mycourses'), $mycourseshtml); 2459 } 2460 break; 2461 2462 case FRONTPAGEALLCOURSELIST: 2463 $availablecourseshtml = $this->frontpage_available_courses(); 2464 $output .= $this->frontpage_part('skipavailablecourses', 'frontpage-available-course-list', 2465 get_string('availablecourses'), $availablecourseshtml); 2466 break; 2467 2468 case FRONTPAGECATEGORYNAMES: 2469 $output .= $this->frontpage_part('skipcategories', 'frontpage-category-names', 2470 get_string('categories'), $this->frontpage_categories_list()); 2471 break; 2472 2473 case FRONTPAGECATEGORYCOMBO: 2474 $output .= $this->frontpage_part('skipcourses', 'frontpage-category-combo', 2475 get_string('courses'), $this->frontpage_combo_list()); 2476 break; 2477 2478 case FRONTPAGECOURSESEARCH: 2479 $output .= $this->box($this->course_search_form(''), 'd-flex justify-content-center'); 2480 break; 2481 2482 } 2483 $output .= '<br />'; 2484 } 2485 2486 return $output; 2487 } 2488 } 2489 2490 /** 2491 * Class storing display options and functions to help display course category and/or courses lists 2492 * 2493 * This is a wrapper for core_course_category objects that also stores display options 2494 * and functions to retrieve sorted and paginated lists of categories/courses. 2495 * 2496 * If theme overrides methods in core_course_renderers that access this class 2497 * it may as well not use this class at all or extend it. 2498 * 2499 * @package core 2500 * @copyright 2013 Marina Glancy 2501 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2502 */ 2503 class coursecat_helper { 2504 /** @var string [none, collapsed, expanded] how (if) display courses list */ 2505 protected $showcourses = 10; /* core_course_renderer::COURSECAT_SHOW_COURSES_COLLAPSED */ 2506 /** @var int depth to expand subcategories in the tree (deeper subcategories will be loaded by AJAX or proceed to category page by clicking on category name) */ 2507 protected $subcatdepth = 1; 2508 /** @var array options to display courses list */ 2509 protected $coursesdisplayoptions = array(); 2510 /** @var array options to display subcategories list */ 2511 protected $categoriesdisplayoptions = array(); 2512 /** @var array additional HTML attributes */ 2513 protected $attributes = array(); 2514 /** @var array search criteria if the list is a search result */ 2515 protected $searchcriteria = null; 2516 2517 /** 2518 * Sets how (if) to show the courses - none, collapsed, expanded, etc. 2519 * 2520 * @param int $showcourses SHOW_COURSES_NONE, SHOW_COURSES_COLLAPSED, SHOW_COURSES_EXPANDED, etc. 2521 * @return coursecat_helper 2522 */ 2523 public function set_show_courses($showcourses) { 2524 $this->showcourses = $showcourses; 2525 // Automatically set the options to preload summary and coursecontacts for core_course_category::get_courses() 2526 // and core_course_category::search_courses(). 2527 $this->coursesdisplayoptions['summary'] = $showcourses >= core_course_renderer::COURSECAT_SHOW_COURSES_AUTO; 2528 $this->coursesdisplayoptions['coursecontacts'] = $showcourses >= core_course_renderer::COURSECAT_SHOW_COURSES_EXPANDED; 2529 $this->coursesdisplayoptions['customfields'] = $showcourses >= core_course_renderer::COURSECAT_SHOW_COURSES_COLLAPSED; 2530 return $this; 2531 } 2532 2533 /** 2534 * Returns how (if) to show the courses - none, collapsed, expanded, etc. 2535 * 2536 * @return int - COURSECAT_SHOW_COURSES_NONE, COURSECAT_SHOW_COURSES_COLLAPSED, COURSECAT_SHOW_COURSES_EXPANDED, etc. 2537 */ 2538 public function get_show_courses() { 2539 return $this->showcourses; 2540 } 2541 2542 /** 2543 * Sets the maximum depth to expand subcategories in the tree 2544 * 2545 * deeper subcategories may be loaded by AJAX or proceed to category page by clicking on category name 2546 * 2547 * @param int $subcatdepth 2548 * @return coursecat_helper 2549 */ 2550 public function set_subcat_depth($subcatdepth) { 2551 $this->subcatdepth = $subcatdepth; 2552 return $this; 2553 } 2554 2555 /** 2556 * Returns the maximum depth to expand subcategories in the tree 2557 * 2558 * deeper subcategories may be loaded by AJAX or proceed to category page by clicking on category name 2559 * 2560 * @return int 2561 */ 2562 public function get_subcat_depth() { 2563 return $this->subcatdepth; 2564 } 2565 2566 /** 2567 * Sets options to display list of courses 2568 * 2569 * Options are later submitted as argument to core_course_category::get_courses() and/or core_course_category::search_courses() 2570 * 2571 * Options that core_course_category::get_courses() accept: 2572 * - recursive - return courses from subcategories as well. Use with care, 2573 * this may be a huge list! 2574 * - summary - preloads fields 'summary' and 'summaryformat' 2575 * - coursecontacts - preloads course contacts 2576 * - customfields - preloads custom fields data 2577 * - isenrolled - preloads indication whether this user is enrolled in the course 2578 * - sort - list of fields to sort. Example 2579 * array('idnumber' => 1, 'shortname' => 1, 'id' => -1) 2580 * will sort by idnumber asc, shortname asc and id desc. 2581 * Default: array('sortorder' => 1) 2582 * Only cached fields may be used for sorting! 2583 * - offset 2584 * - limit - maximum number of children to return, 0 or null for no limit 2585 * 2586 * Options summary and coursecontacts are filled automatically in the set_show_courses() 2587 * 2588 * Also renderer can set here any additional options it wants to pass between renderer functions. 2589 * 2590 * @param array $options 2591 * @return coursecat_helper 2592 */ 2593 public function set_courses_display_options($options) { 2594 $this->coursesdisplayoptions = $options; 2595 $this->set_show_courses($this->showcourses); // this will calculate special display options 2596 return $this; 2597 } 2598 2599 /** 2600 * Sets one option to display list of courses 2601 * 2602 * @see coursecat_helper::set_courses_display_options() 2603 * 2604 * @param string $key 2605 * @param mixed $value 2606 * @return coursecat_helper 2607 */ 2608 public function set_courses_display_option($key, $value) { 2609 $this->coursesdisplayoptions[$key] = $value; 2610 return $this; 2611 } 2612 2613 /** 2614 * Return the specified option to display list of courses 2615 * 2616 * @param string $optionname option name 2617 * @param mixed $defaultvalue default value for option if it is not specified 2618 * @return mixed 2619 */ 2620 public function get_courses_display_option($optionname, $defaultvalue = null) { 2621 if (array_key_exists($optionname, $this->coursesdisplayoptions)) { 2622 return $this->coursesdisplayoptions[$optionname]; 2623 } else { 2624 return $defaultvalue; 2625 } 2626 } 2627 2628 /** 2629 * Returns all options to display the courses 2630 * 2631 * This array is usually passed to {@link core_course_category::get_courses()} or 2632 * {@link core_course_category::search_courses()} 2633 * 2634 * @return array 2635 */ 2636 public function get_courses_display_options() { 2637 return $this->coursesdisplayoptions; 2638 } 2639 2640 /** 2641 * Sets options to display list of subcategories 2642 * 2643 * Options 'sort', 'offset' and 'limit' are passed to core_course_category::get_children(). 2644 * Any other options may be used by renderer functions 2645 * 2646 * @param array $options 2647 * @return coursecat_helper 2648 */ 2649 public function set_categories_display_options($options) { 2650 $this->categoriesdisplayoptions = $options; 2651 return $this; 2652 } 2653 2654 /** 2655 * Return the specified option to display list of subcategories 2656 * 2657 * @param string $optionname option name 2658 * @param mixed $defaultvalue default value for option if it is not specified 2659 * @return mixed 2660 */ 2661 public function get_categories_display_option($optionname, $defaultvalue = null) { 2662 if (array_key_exists($optionname, $this->categoriesdisplayoptions)) { 2663 return $this->categoriesdisplayoptions[$optionname]; 2664 } else { 2665 return $defaultvalue; 2666 } 2667 } 2668 2669 /** 2670 * Returns all options to display list of subcategories 2671 * 2672 * This array is usually passed to {@link core_course_category::get_children()} 2673 * 2674 * @return array 2675 */ 2676 public function get_categories_display_options() { 2677 return $this->categoriesdisplayoptions; 2678 } 2679 2680 /** 2681 * Sets additional general options to pass between renderer functions, usually HTML attributes 2682 * 2683 * @param array $attributes 2684 * @return coursecat_helper 2685 */ 2686 public function set_attributes($attributes) { 2687 $this->attributes = $attributes; 2688 return $this; 2689 } 2690 2691 /** 2692 * Return all attributes and erases them so they are not applied again 2693 * 2694 * @param string $classname adds additional class name to the beginning of $attributes['class'] 2695 * @return array 2696 */ 2697 public function get_and_erase_attributes($classname) { 2698 $attributes = $this->attributes; 2699 $this->attributes = array(); 2700 if (empty($attributes['class'])) { 2701 $attributes['class'] = ''; 2702 } 2703 $attributes['class'] = $classname . ' '. $attributes['class']; 2704 return $attributes; 2705 } 2706 2707 /** 2708 * Sets the search criteria if the course is a search result 2709 * 2710 * Search string will be used to highlight terms in course name and description 2711 * 2712 * @param array $searchcriteria 2713 * @return coursecat_helper 2714 */ 2715 public function set_search_criteria($searchcriteria) { 2716 $this->searchcriteria = $searchcriteria; 2717 return $this; 2718 } 2719 2720 /** 2721 * Returns formatted and filtered description of the given category 2722 * 2723 * @param core_course_category $coursecat category 2724 * @param stdClass|array $options format options, by default [noclean,overflowdiv], 2725 * if context is not specified it will be added automatically 2726 * @return string|null 2727 */ 2728 public function get_category_formatted_description($coursecat, $options = null) { 2729 if ($coursecat->id && $coursecat->is_uservisible() && !empty($coursecat->description)) { 2730 if (!isset($coursecat->descriptionformat)) { 2731 $descriptionformat = FORMAT_MOODLE; 2732 } else { 2733 $descriptionformat = $coursecat->descriptionformat; 2734 } 2735 if ($options === null) { 2736 $options = array('noclean' => true, 'overflowdiv' => true); 2737 } else { 2738 $options = (array)$options; 2739 } 2740 $context = context_coursecat::instance($coursecat->id); 2741 if (!isset($options['context'])) { 2742 $options['context'] = $context; 2743 } 2744 $text = file_rewrite_pluginfile_urls($coursecat->description, 2745 'pluginfile.php', $context->id, 'coursecat', 'description', null); 2746 return format_text($text, $descriptionformat, $options); 2747 } 2748 return null; 2749 } 2750 2751 /** 2752 * Returns given course's summary with proper embedded files urls and formatted 2753 * 2754 * @param core_course_list_element $course 2755 * @param array|stdClass $options additional formatting options 2756 * @return string 2757 */ 2758 public function get_course_formatted_summary($course, $options = array()) { 2759 global $CFG; 2760 require_once($CFG->libdir. '/filelib.php'); 2761 if (!$course->has_summary()) { 2762 return ''; 2763 } 2764 $options = (array)$options; 2765 $context = context_course::instance($course->id); 2766 if (!isset($options['context'])) { 2767 // TODO see MDL-38521 2768 // option 1 (current), page context - no code required 2769 // option 2, system context 2770 // $options['context'] = context_system::instance(); 2771 // option 3, course context: 2772 // $options['context'] = $context; 2773 // option 4, course category context: 2774 // $options['context'] = $context->get_parent_context(); 2775 } 2776 $summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', null); 2777 $summary = format_text($summary, $course->summaryformat, $options, $course->id); 2778 if (!empty($this->searchcriteria['search'])) { 2779 $summary = highlight($this->searchcriteria['search'], $summary); 2780 } 2781 return $summary; 2782 } 2783 2784 /** 2785 * Returns course name as it is configured to appear in courses lists formatted to course context 2786 * 2787 * @param core_course_list_element $course 2788 * @param array|stdClass $options additional formatting options 2789 * @return string 2790 */ 2791 public function get_course_formatted_name($course, $options = array()) { 2792 $options = (array)$options; 2793 if (!isset($options['context'])) { 2794 $options['context'] = context_course::instance($course->id); 2795 } 2796 $name = format_string(get_course_display_name_for_list($course), true, $options); 2797 if (!empty($this->searchcriteria['search'])) { 2798 $name = highlight($this->searchcriteria['search'], $name); 2799 } 2800 return $name; 2801 } 2802 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body