Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Contains renderers for the course management pages.
  19   *
  20   * @package core_course
  21   * @copyright 2013 Sam Hemelryk
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die;
  26  
  27  require_once($CFG->dirroot.'/course/renderer.php');
  28  
  29  /**
  30   * Main renderer for the course management pages.
  31   *
  32   * @package core_course
  33   * @copyright 2013 Sam Hemelryk
  34   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class core_course_management_renderer extends plugin_renderer_base {
  37  
  38      /**
  39       * Initialises the JS required to enhance the management interface.
  40       *
  41       * Thunderbirds are go, this function kicks into gear the JS that makes the
  42       * course management pages that much cooler.
  43       */
  44      public function enhance_management_interface() {
  45          $this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
  46          $this->page->requires->strings_for_js(
  47              array(
  48                  'show',
  49                  'showcategory',
  50                  'hide',
  51                  'expand',
  52                  'expandcategory',
  53                  'collapse',
  54                  'collapsecategory',
  55                  'confirmcoursemove',
  56                  'move',
  57                  'cancel',
  58                  'confirm'
  59              ),
  60              'moodle'
  61          );
  62      }
  63  
  64      /**
  65       * Displays a heading for the management pages.
  66       *
  67       * @param string $heading The heading to display
  68       * @param string|null $viewmode The current view mode if there are options.
  69       * @param int|null $categoryid The currently selected category if there is one.
  70       * @return string
  71       */
  72      public function management_heading($heading, $viewmode = null, $categoryid = null) {
  73          $html = html_writer::start_div('coursecat-management-header clearfix');
  74          if (!empty($heading)) {
  75              $html .= $this->heading($heading);
  76          }
  77          if ($viewmode !== null) {
  78              $html .= html_writer::start_div();
  79              $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
  80              if ($viewmode === 'courses') {
  81                  $categories = core_course_category::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
  82                  $nothing = false;
  83                  if ($categoryid === null) {
  84                      $nothing = array('' => get_string('selectacategory'));
  85                      $categoryid = '';
  86                  }
  87                  $select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
  88                  $select->attributes['aria-label'] = get_string('selectacategory');
  89                  $html .= $this->render($select);
  90              }
  91              $html .= html_writer::end_div();
  92          }
  93          $html .= html_writer::end_div();
  94          return $html;
  95      }
  96  
  97      /**
  98       * Prepares the form element for the course category listing bulk actions.
  99       *
 100       * @return string
 101       */
 102      public function management_form_start() {
 103          $form = array('action' => $this->page->url->out(), 'method' => 'POST', 'id' => 'coursecat-management');
 104  
 105          $html = html_writer::start_tag('form', $form);
 106          $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
 107          $html .=  html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'bulkaction'));
 108          return $html;
 109      }
 110  
 111      /**
 112       * Closes the course category bulk management form.
 113       *
 114       * @return string
 115       */
 116      public function management_form_end() {
 117          return html_writer::end_tag('form');
 118      }
 119  
 120      /**
 121       * Presents a course category listing.
 122       *
 123       * @param core_course_category $category The currently selected category. Also the category to highlight in the listing.
 124       * @return string
 125       */
 126      public function category_listing(core_course_category $category = null) {
 127  
 128          if ($category === null) {
 129              $selectedparents = array();
 130              $selectedcategory = null;
 131          } else {
 132              $selectedparents = $category->get_parents();
 133              $selectedparents[] = $category->id;
 134              $selectedcategory = $category->id;
 135          }
 136          $catatlevel = \core_course\management\helper::get_expanded_categories('');
 137          $catatlevel[] = array_shift($selectedparents);
 138          $catatlevel = array_unique($catatlevel);
 139  
 140          $listing = core_course_category::top()->get_children();
 141  
 142          $attributes = array(
 143                  'class' => 'ml-1 list-unstyled',
 144                  'role' => 'tree',
 145                  'aria-labelledby' => 'category-listing-title'
 146          );
 147  
 148          $html  = html_writer::start_div('category-listing card w-100');
 149          $html .= html_writer::tag('h3', get_string('categories'),
 150                  array('class' => 'card-header', 'id' => 'category-listing-title'));
 151          $html .= html_writer::start_div('card-body');
 152          $html .= $this->category_listing_actions($category);
 153          $html .= html_writer::start_tag('ul', $attributes);
 154          foreach ($listing as $listitem) {
 155              // Render each category in the listing.
 156              $subcategories = array();
 157              if (in_array($listitem->id, $catatlevel)) {
 158                  $subcategories = $listitem->get_children();
 159              }
 160              $html .= $this->category_listitem(
 161                      $listitem,
 162                      $subcategories,
 163                      $listitem->get_children_count(),
 164                      $selectedcategory,
 165                      $selectedparents
 166              );
 167          }
 168          $html .= html_writer::end_tag('ul');
 169          $html .= $this->category_bulk_actions($category);
 170          $html .= html_writer::end_div();
 171          $html .= html_writer::end_div();
 172          return $html;
 173      }
 174  
 175      /**
 176       * Renders a category list item.
 177       *
 178       * This function gets called recursively to render sub categories.
 179       *
 180       * @param core_course_category $category The category to render as listitem.
 181       * @param core_course_category[] $subcategories The subcategories belonging to the category being rented.
 182       * @param int $totalsubcategories The total number of sub categories.
 183       * @param int $selectedcategory The currently selected category
 184       * @param int[] $selectedcategories The path to the selected category and its ID.
 185       * @return string
 186       */
 187      public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
 188              $selectedcategory = null, $selectedcategories = array()) {
 189  
 190          $isexpandable = ($totalsubcategories > 0);
 191          $isexpanded = (!empty($subcategories));
 192          $activecategory = ($selectedcategory === $category->id);
 193          $attributes = array(
 194                  'class' => 'listitem listitem-category list-group-item list-group-item-action',
 195                  'data-id' => $category->id,
 196                  'data-expandable' => $isexpandable ? '1' : '0',
 197                  'data-expanded' => $isexpanded ? '1' : '0',
 198                  'data-selected' => $activecategory ? '1' : '0',
 199                  'data-visible' => $category->visible ? '1' : '0',
 200                  'role' => 'treeitem',
 201                  'aria-expanded' => $isexpanded ? 'true' : 'false'
 202          );
 203          $text = $category->get_formatted_name();
 204          if (($parent = $category->get_parent_coursecat()) && $parent->id) {
 205              $a = new stdClass;
 206              $a->category = $text;
 207              $a->parentcategory = $parent->get_formatted_name();
 208              $textlabel = get_string('categorysubcategoryof', 'moodle', $a);
 209          }
 210          $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
 211          $bcatinput = array(
 212                  'id' => 'categorylistitem' . $category->id,
 213                  'type' => 'checkbox',
 214                  'name' => 'bcat[]',
 215                  'value' => $category->id,
 216                  'class' => 'bulk-action-checkbox custom-control-input',
 217                  'data-action' => 'select'
 218          );
 219  
 220          $checkboxclass = '';
 221          if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
 222              // Very very hardcoded here.
 223              $checkboxclass = 'd-none';
 224          }
 225  
 226          $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
 227          if ($isexpanded) {
 228              $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'),
 229                      'moodle', array('class' => 'tree-icon', 'title' => ''));
 230              $icon = html_writer::link(
 231                      $viewcaturl,
 232                      $icon,
 233                      array(
 234                              'class' => 'float-left',
 235                              'data-action' => 'collapse',
 236                              'title' => get_string('collapsecategory', 'moodle', $text),
 237                              'aria-controls' => 'subcategoryof'.$category->id
 238                      )
 239              );
 240          } else if ($isexpandable) {
 241              $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'),
 242                      'moodle', array('class' => 'tree-icon', 'title' => ''));
 243              $icon = html_writer::link(
 244                      $viewcaturl,
 245                      $icon,
 246                      array(
 247                              'class' => 'float-left',
 248                              'data-action' => 'expand',
 249                              'title' => get_string('expandcategory', 'moodle', $text)
 250                      )
 251              );
 252          } else {
 253              $icon = $this->output->pix_icon(
 254                      'i/empty',
 255                      '',
 256                      'moodle',
 257                      array('class' => 'tree-icon'));
 258              $icon = html_writer::span($icon, 'float-left');
 259          }
 260          $actions = \core_course\management\helper::get_category_listitem_actions($category);
 261          $hasactions = !empty($actions) || $category->can_create_course();
 262  
 263          $html = html_writer::start_tag('li', $attributes);
 264          $html .= html_writer::start_div('clearfix');
 265          $html .= html_writer::start_div('float-left ' . $checkboxclass);
 266          $html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
 267          $html .= html_writer::empty_tag('input', $bcatinput);
 268          $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
 269          $html .= html_writer::tag('label', $labeltext, array(
 270              'class' => 'custom-control-label',
 271              'for' => 'categorylistitem' . $category->id));
 272          $html .= html_writer::end_div();
 273          $html .= html_writer::end_div();
 274          $html .= $icon;
 275          if ($hasactions) {
 276              $textattributes = array('class' => 'float-left categoryname aalink');
 277          } else {
 278              $textattributes = array('class' => 'float-left categoryname without-actions');
 279          }
 280          if (isset($textlabel)) {
 281              $textattributes['aria-label'] = $textlabel;
 282          }
 283          $html .= html_writer::link($viewcaturl, $text, $textattributes);
 284          $html .= html_writer::start_div('float-right d-flex');
 285          if ($category->idnumber) {
 286              $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'text-muted idnumber'));
 287          }
 288          if ($hasactions) {
 289              $html .= $this->category_listitem_actions($category, $actions);
 290          }
 291          $countid = 'course-count-'.$category->id;
 292          $html .= html_writer::span(
 293                  html_writer::span($category->get_courses_count()) .
 294                  html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
 295                  $courseicon,
 296                  'course-count text-muted',
 297                  array('aria-labelledby' => $countid)
 298          );
 299          $html .= html_writer::end_div();
 300          $html .= html_writer::end_div();
 301          if ($isexpanded) {
 302              $html .= html_writer::start_tag('ul',
 303                      array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
 304              $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
 305              $catatlevel[] = array_shift($selectedcategories);
 306              $catatlevel = array_unique($catatlevel);
 307              foreach ($subcategories as $listitem) {
 308                  $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
 309                  $html .= $this->category_listitem(
 310                          $listitem,
 311                          $childcategories,
 312                          $listitem->get_children_count(),
 313                          $selectedcategory,
 314                          $selectedcategories
 315                  );
 316              }
 317              $html .= html_writer::end_tag('ul');
 318          }
 319          $html .= html_writer::end_tag('li');
 320          return $html;
 321      }
 322  
 323      /**
 324       * Renderers the actions that are possible for the course category listing.
 325       *
 326       * These are not the actions associated with an individual category listing.
 327       * That happens through category_listitem_actions.
 328       *
 329       * @param core_course_category $category
 330       * @return string
 331       */
 332      public function category_listing_actions(core_course_category $category = null) {
 333          $actions = array();
 334  
 335          $cancreatecategory = $category && $category->can_create_subcategory();
 336          $cancreatecategory = $cancreatecategory || core_course_category::can_create_top_level_category();
 337          if ($category === null) {
 338              $category = core_course_category::top();
 339          }
 340  
 341          if ($cancreatecategory) {
 342              $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
 343              $actions[] = html_writer::link($url, get_string('createnewcategory'), array('class' => 'btn btn-secondary'));
 344          }
 345          if (core_course_category::can_approve_course_requests()) {
 346              $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
 347          }
 348          if (count($actions) === 0) {
 349              return '';
 350          }
 351          return html_writer::div(join(' ', $actions), 'listing-actions category-listing-actions mb-3');
 352      }
 353  
 354      /**
 355       * Renderers the actions for individual category list items.
 356       *
 357       * @param core_course_category $category
 358       * @param array $actions
 359       * @return string
 360       */
 361      public function category_listitem_actions(core_course_category $category, array $actions = null) {
 362          if ($actions === null) {
 363              $actions = \core_course\management\helper::get_category_listitem_actions($category);
 364          }
 365          $menu = new action_menu();
 366          $menu->attributes['class'] .= ' category-item-actions item-actions';
 367          $hasitems = false;
 368          foreach ($actions as $key => $action) {
 369              $hasitems = true;
 370              $menu->add(new action_menu_link(
 371                  $action['url'],
 372                  $action['icon'],
 373                  $action['string'],
 374                  in_array($key, array('show', 'hide', 'moveup', 'movedown')),
 375                  array('data-action' => $key, 'class' => 'action-'.$key)
 376              ));
 377          }
 378          if (!$hasitems) {
 379              return '';
 380          }
 381          return $this->render($menu);
 382      }
 383  
 384      public function render_action_menu($menu) {
 385          return $this->output->render($menu);
 386      }
 387  
 388      /**
 389       * Renders bulk actions for categories.
 390       *
 391       * @param core_course_category $category The currently selected category if there is one.
 392       * @return string
 393       */
 394      public function category_bulk_actions(core_course_category $category = null) {
 395          // Resort courses.
 396          // Change parent.
 397          if (!core_course_category::can_resort_any() && !core_course_category::can_change_parent_any()) {
 398              return '';
 399          }
 400          $strgo = new lang_string('go');
 401  
 402          $html  = html_writer::start_div('category-bulk-actions bulk-actions');
 403          $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
 404          if (core_course_category::can_resort_any()) {
 405              $selectoptions = array(
 406                  'selectedcategories' => get_string('selectedcategories'),
 407                  'allcategories' => get_string('allcategories')
 408              );
 409              $form = html_writer::start_div();
 410              if ($category) {
 411                  $selectoptions = array('thiscategory' => get_string('thiscategory')) + $selectoptions;
 412                  $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentcategoryid', 'value' => $category->id));
 413              }
 414              $form .= html_writer::div(
 415                  html_writer::select(
 416                      $selectoptions,
 417                      'selectsortby',
 418                      'selectedcategories',
 419                      false,
 420                      array('aria-label' => get_string('selectcategorysort'))
 421                  )
 422              );
 423              $form .= html_writer::div(
 424                  html_writer::select(
 425                      array(
 426                          'name' => get_string('sortbyx', 'moodle', get_string('categoryname')),
 427                          'namedesc' => get_string('sortbyxreverse', 'moodle', get_string('categoryname')),
 428                          'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercoursecategory')),
 429                          'idnumberdesc' => get_string('sortbyxreverse' , 'moodle' , get_string('idnumbercoursecategory')),
 430                          'none' => get_string('dontsortcategories')
 431                      ),
 432                      'resortcategoriesby',
 433                      'name',
 434                      false,
 435                      array('aria-label' => get_string('selectcategorysortby'), 'class' => 'mt-1')
 436                  )
 437              );
 438              $form .= html_writer::div(
 439                  html_writer::select(
 440                      array(
 441                          'fullname' => get_string('sortbyx', 'moodle', get_string('fullnamecourse')),
 442                          'fullnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse')),
 443                          'shortname' => get_string('sortbyx', 'moodle', get_string('shortnamecourse')),
 444                          'shortnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse')),
 445                          'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercourse')),
 446                          'idnumberdesc' => get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse')),
 447                          'timecreated' => get_string('sortbyx', 'moodle', get_string('timecreatedcourse')),
 448                          'timecreateddesc' => get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')),
 449                          'none' => get_string('dontsortcourses')
 450                      ),
 451                      'resortcoursesby',
 452                      'fullname',
 453                      false,
 454                      array('aria-label' => get_string('selectcoursesortby'), 'class' => 'mt-1')
 455                  )
 456              );
 457              $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort',
 458                  'value' => get_string('sort'), 'class' => 'btn btn-secondary my-1'));
 459              $form .= html_writer::end_div();
 460  
 461              $html .= html_writer::start_div('detail-pair row yui3-g my-1');
 462              $html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key col-md-3 yui3-u-1-4');
 463              $html .= html_writer::div($form, 'pair-value col-md-9 yui3-u-3-4');
 464              $html .= html_writer::end_div();
 465          }
 466          if (core_course_category::can_change_parent_any()) {
 467              $options = array();
 468              if (core_course_category::top()->has_manage_capability()) {
 469                  $options[0] = core_course_category::top()->get_formatted_name();
 470              }
 471              $options += core_course_category::make_categories_list('moodle/category:manage');
 472              $select = html_writer::select(
 473                  $options,
 474                  'movecategoriesto',
 475                  '',
 476                  array('' => 'choosedots'),
 477                  array('aria-labelledby' => 'moveselectedcategoriesto', 'class' => 'mr-1')
 478              );
 479              $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'),
 480                  'class' => 'btn btn-secondary');
 481              $html .= $this->detail_pair(
 482                  html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
 483                  $select . html_writer::empty_tag('input', $submit)
 484              );
 485          }
 486          $html .= html_writer::end_div();
 487          return $html;
 488      }
 489  
 490      /**
 491       * Renders a course listing.
 492       *
 493       * @param core_course_category $category The currently selected category. This is what the listing is focused on.
 494       * @param core_course_list_element $course The currently selected course.
 495       * @param int $page The page being displayed.
 496       * @param int $perpage The number of courses to display per page.
 497       * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
 498       * @return string
 499       */
 500      public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
 501              $page = 0, $perpage = 20, $viewmode = 'default') {
 502  
 503          if ($category === null) {
 504              $html = html_writer::start_div('select-a-category');
 505              $html .= html_writer::tag('h3', get_string('courses'),
 506                      array('id' => 'course-listing-title', 'tabindex' => '0'));
 507              $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
 508              $html .= html_writer::end_div();
 509              return $html;
 510          }
 511  
 512          $page = max($page, 0);
 513          $perpage = max($perpage, 2);
 514          $totalcourses = $category->coursecount;
 515          $totalpages = ceil($totalcourses / $perpage);
 516          if ($page > $totalpages - 1) {
 517              $page = $totalpages - 1;
 518          }
 519          $options = array(
 520                  'offset' => $page * $perpage,
 521                  'limit' => $perpage
 522          );
 523          $courseid = isset($course) ? $course->id : null;
 524          $class = '';
 525          if ($page === 0) {
 526              $class .= ' firstpage';
 527          }
 528          if ($page + 1 === (int)$totalpages) {
 529              $class .= ' lastpage';
 530          }
 531  
 532          $html  = html_writer::start_div('card course-listing w-100'.$class, array(
 533                  'data-category' => $category->id,
 534                  'data-page' => $page,
 535                  'data-totalpages' => $totalpages,
 536                  'data-totalcourses' => $totalcourses,
 537                  'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
 538          ));
 539          $html .= html_writer::tag('h3', $category->get_formatted_name(),
 540                  array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
 541          $html .= html_writer::start_div('card-body');
 542          $html .= $this->course_listing_actions($category, $course, $perpage);
 543          $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
 544          $html .= html_writer::start_tag('ul', array('class' => 'ml course-list'));
 545          foreach ($category->get_courses($options) as $listitem) {
 546              $html .= $this->course_listitem($category, $listitem, $courseid);
 547          }
 548          $html .= html_writer::end_tag('ul');
 549          $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
 550          $html .= $this->course_bulk_actions($category);
 551          $html .= html_writer::end_div();
 552          $html .= html_writer::end_div();
 553          return $html;
 554      }
 555  
 556      /**
 557       * Renders pagination for a course listing.
 558       *
 559       * @param core_course_category $category The category to produce pagination for.
 560       * @param int $page The current page.
 561       * @param int $perpage The number of courses to display per page.
 562       * @param bool $showtotals Set to true to show the total number of courses and what is being displayed.
 563       * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
 564       * @return string
 565       */
 566      protected function listing_pagination(core_course_category $category, $page, $perpage, $showtotals = false,
 567                                            $viewmode = 'default') {
 568          $html = '';
 569          $totalcourses = $category->get_courses_count();
 570          $totalpages = ceil($totalcourses / $perpage);
 571          if ($showtotals) {
 572              if ($totalpages == 0) {
 573                  $str = get_string('nocoursesyet');
 574              } else if ($totalpages == 1) {
 575                  $str = get_string('showingacourses', 'moodle', $totalcourses);
 576              } else {
 577                  $a = new stdClass;
 578                  $a->start = ($page * $perpage) + 1;
 579                  $a->end = min((($page + 1) * $perpage), $totalcourses);
 580                  $a->total = $totalcourses;
 581                  $str = get_string('showingxofycourses', 'moodle', $a);
 582              }
 583              $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
 584          }
 585  
 586          if ($viewmode !== 'default') {
 587              $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id,
 588                  'view' => $viewmode));
 589          } else {
 590              $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
 591          }
 592  
 593          $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
 594          return $html;
 595      }
 596  
 597      /**
 598       * Renderers a course list item.
 599       *
 600       * This function will be called for every course being displayed by course_listing.
 601       *
 602       * @param core_course_category $category The currently selected category and the category the course belongs to.
 603       * @param core_course_list_element $course The course to produce HTML for.
 604       * @param int $selectedcourse The id of the currently selected course.
 605       * @return string
 606       */
 607      public function course_listitem(core_course_category $category, core_course_list_element $course, $selectedcourse) {
 608  
 609          $text = $course->get_formatted_name();
 610          $attributes = array(
 611                  'class' => 'listitem listitem-course list-group-item list-group-item-action',
 612                  'data-id' => $course->id,
 613                  'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
 614                  'data-visible' => $course->visible ? '1' : '0'
 615          );
 616  
 617          $bulkcourseinput = array(
 618                  'id' => 'courselistitem' . $course->id,
 619                  'type' => 'checkbox',
 620                  'name' => 'bc[]',
 621                  'value' => $course->id,
 622                  'class' => 'bulk-action-checkbox custom-control-input',
 623                  'data-action' => 'select'
 624          );
 625  
 626          $checkboxclass = '';
 627          if (!$category->has_manage_capability()) {
 628              // Very very hardcoded here.
 629              $checkboxclass = 'd-none';
 630          }
 631  
 632          $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
 633  
 634          $html  = html_writer::start_tag('li', $attributes);
 635          $html .= html_writer::start_div('clearfix');
 636  
 637          if ($category->can_resort_courses()) {
 638              // In order for dnd to be available the user must be able to resort the category children..
 639              $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
 640          }
 641  
 642          $html .= html_writer::start_div('float-left ' . $checkboxclass);
 643          $html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
 644          $html .= html_writer::empty_tag('input', $bulkcourseinput);
 645          $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
 646          $html .= html_writer::tag('label', $labeltext, array(
 647              'class' => 'custom-control-label',
 648              'for' => 'courselistitem' . $course->id));
 649          $html .= html_writer::end_div();
 650          $html .= html_writer::end_div();
 651          $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
 652          $html .= html_writer::start_div('float-right');
 653          if ($course->idnumber) {
 654              $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
 655          }
 656          $html .= $this->course_listitem_actions($category, $course);
 657          $html .= html_writer::end_div();
 658          $html .= html_writer::end_div();
 659          $html .= html_writer::end_tag('li');
 660          return $html;
 661      }
 662  
 663      /**
 664       * Renderers actions for the course listing.
 665       *
 666       * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
 667       *
 668       * @param core_course_category $category
 669       * @param core_course_list_element $course The currently selected course.
 670       * @param int $perpage
 671       * @return string
 672       */
 673      public function course_listing_actions(core_course_category $category, core_course_list_element $course = null, $perpage = 20) {
 674          $actions = array();
 675          if ($category->can_create_course()) {
 676              $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
 677              $actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-secondary'));
 678          }
 679          if ($category->can_request_course()) {
 680              // Request a new course.
 681              $url = new moodle_url('/course/request.php', array('category' => $category->id, 'return' => 'management'));
 682              $actions[] = html_writer::link($url, get_string('requestcourse'));
 683          }
 684          if ($category->can_resort_courses()) {
 685              $params = $this->page->url->params();
 686              $params['action'] = 'resortcourses';
 687              $params['sesskey'] = sesskey();
 688              $baseurl = new moodle_url('/course/management.php', $params);
 689              $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
 690              $fullnameurldesc = new moodle_url($baseurl, array('resort' => 'fullnamedesc'));
 691              $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
 692              $shortnameurldesc = new moodle_url($baseurl, array('resort' => 'shortnamedesc'));
 693              $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
 694              $idnumberdescurl = new moodle_url($baseurl, array('resort' => 'idnumberdesc'));
 695              $timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
 696              $timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
 697              $menu = new action_menu(array(
 698                      new action_menu_link_secondary($fullnameurl,
 699                              null,
 700                              get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
 701                      new action_menu_link_secondary($fullnameurldesc,
 702                              null,
 703                              get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
 704                      new action_menu_link_secondary($shortnameurl,
 705                              null,
 706                              get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
 707                      new action_menu_link_secondary($shortnameurldesc,
 708                              null,
 709                              get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
 710                      new action_menu_link_secondary($idnumberurl,
 711                              null,
 712                              get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
 713                      new action_menu_link_secondary($idnumberdescurl,
 714                              null,
 715                              get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
 716                      new action_menu_link_secondary($timecreatedurl,
 717                              null,
 718                              get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
 719                      new action_menu_link_secondary($timecreateddescurl,
 720                              null,
 721                              get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
 722              ));
 723              $menu->set_menu_trigger(get_string('resortcourses'));
 724              $actions[] = $this->render($menu);
 725          }
 726          $strall = get_string('all');
 727          $menu = new action_menu(array(
 728                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
 729                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
 730                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
 731                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
 732                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
 733                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
 734          ));
 735          if ((int)$perpage === 999) {
 736              $perpage = $strall;
 737          }
 738          $menu->attributes['class'] .= ' courses-per-page';
 739          $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
 740          $actions[] = $this->render($menu);
 741          return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
 742      }
 743  
 744      /**
 745       * Renderers actions for individual course actions.
 746       *
 747       * @param core_course_category $category The currently selected category.
 748       * @param core_course_list_element  $course The course to renderer actions for.
 749       * @return string
 750       */
 751      public function course_listitem_actions(core_course_category $category, core_course_list_element $course) {
 752          $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
 753          if (empty($actions)) {
 754              return '';
 755          }
 756          $actionshtml = array();
 757          foreach ($actions as $action) {
 758              $action['attributes']['role'] = 'button';
 759              $actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
 760          }
 761          return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
 762      }
 763  
 764      /**
 765       * Renderers bulk actions that can be performed on courses.
 766       *
 767       * @param core_course_category $category The currently selected category and the category in which courses that
 768       *      are selectable belong.
 769       * @return string
 770       */
 771      public function course_bulk_actions(core_course_category $category) {
 772          $html  = html_writer::start_div('course-bulk-actions bulk-actions');
 773          if ($category->can_move_courses_out_of()) {
 774              $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
 775              $options = core_course_category::make_categories_list('moodle/category:manage');
 776              $select = html_writer::select(
 777                  $options,
 778                  'movecoursesto',
 779                  '',
 780                  array('' => 'choosedots'),
 781                  array('aria-labelledby' => 'moveselectedcoursesto', 'class' => 'mr-1')
 782              );
 783              $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
 784                  'class' => 'btn btn-secondary');
 785              $html .= $this->detail_pair(
 786                  html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
 787                  $select . html_writer::empty_tag('input', $submit)
 788              );
 789          }
 790          $html .= html_writer::end_div();
 791          return $html;
 792      }
 793  
 794      /**
 795       * Renderers bulk actions that can be performed on courses in search returns
 796       *
 797       * @return string
 798       */
 799      public function course_search_bulk_actions() {
 800          $html  = html_writer::start_div('course-bulk-actions bulk-actions');
 801          $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
 802          $options = core_course_category::make_categories_list('moodle/category:manage');
 803          $select = html_writer::select(
 804              $options,
 805              'movecoursesto',
 806              '',
 807              array('' => 'choosedots'),
 808              array('aria-labelledby' => 'moveselectedcoursesto')
 809          );
 810          $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
 811              'class' => 'btn btn-secondary');
 812          $html .= $this->detail_pair(
 813              html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
 814              $select . html_writer::empty_tag('input', $submit)
 815          );
 816          $html .= html_writer::end_div();
 817          return $html;
 818      }
 819  
 820      /**
 821       * Renderers detailed course information.
 822       *
 823       * @param core_course_list_element $course The course to display details for.
 824       * @return string
 825       */
 826      public function course_detail(core_course_list_element $course) {
 827          $details = \core_course\management\helper::get_course_detail_array($course);
 828          $fullname = $details['fullname']['value'];
 829  
 830          $html = html_writer::start_div('course-detail card');
 831          $html .= html_writer::start_div('card-header');
 832          $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title',
 833                  'class' => 'card-title', 'tabindex' => '0'));
 834          $html .= html_writer::end_div();
 835          $html .= html_writer::start_div('card-body');
 836          $html .= $this->course_detail_actions($course);
 837          foreach ($details as $class => $data) {
 838              $html .= $this->detail_pair($data['key'], $data['value'], $class);
 839          }
 840          $html .= html_writer::end_div();
 841          $html .= html_writer::end_div();
 842          return $html;
 843      }
 844  
 845      /**
 846       * Renderers a key value pair of information for display.
 847       *
 848       * @param string $key
 849       * @param string $value
 850       * @param string $class
 851       * @return string
 852       */
 853      protected function detail_pair($key, $value, $class ='') {
 854          $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
 855          $html .= html_writer::div(html_writer::span($key), 'pair-key col-md-3 yui3-u-1-4 font-weight-bold');
 856          $html .= html_writer::div(html_writer::span($value), 'pair-value col-md-8 yui3-u-3-4');
 857          $html .= html_writer::end_div();
 858          return $html;
 859      }
 860  
 861      /**
 862       * A collection of actions for a course.
 863       *
 864       * @param core_course_list_element $course The course to display actions for.
 865       * @return string
 866       */
 867      public function course_detail_actions(core_course_list_element $course) {
 868          $actions = \core_course\management\helper::get_course_detail_actions($course);
 869          if (empty($actions)) {
 870              return '';
 871          }
 872          $options = array();
 873          foreach ($actions as $action) {
 874              $options[] = $this->action_link($action['url'], $action['string'], null,
 875                      array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
 876          }
 877          return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
 878      }
 879  
 880      /**
 881       * Creates an action button (styled link)
 882       *
 883       * @param moodle_url $url The URL to go to when clicked.
 884       * @param string $text The text for the button.
 885       * @param string $id An id to give the button.
 886       * @param string $class A class to give the button.
 887       * @param array $attributes Any additional attributes
 888       * @return string
 889       */
 890      protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
 891          if (isset($attributes['class'])) {
 892              $attributes['class'] .= ' yui3-button';
 893          } else {
 894              $attributes['class'] = 'yui3-button';
 895          }
 896          if (!is_null($id)) {
 897              $attributes['id'] = $id;
 898          }
 899          if (!is_null($class)) {
 900              $attributes['class'] .= ' '.$class;
 901          }
 902          if (is_null($title)) {
 903              $title = $text;
 904          }
 905          $attributes['title'] = $title;
 906          if (!isset($attributes['role'])) {
 907              $attributes['role'] = 'button';
 908          }
 909          return html_writer::link($url, $text, $attributes);
 910      }
 911  
 912      /**
 913       * Opens a grid.
 914       *
 915       * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
 916       *
 917       * @param string $id An id to give this grid.
 918       * @param string $class A class to give this grid.
 919       * @return string
 920       */
 921      public function grid_start($id = null, $class = null) {
 922          $gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
 923          if (is_null($class)) {
 924              $class = $gridclass;
 925          } else {
 926              $class .= ' ' . $gridclass;
 927          }
 928          $attributes = array();
 929          if (!is_null($id)) {
 930              $attributes['id'] = $id;
 931          }
 932          return html_writer::start_div($class, $attributes);
 933      }
 934  
 935      /**
 936       * Closes the grid.
 937       *
 938       * @return string
 939       */
 940      public function grid_end() {
 941          return html_writer::end_div();
 942      }
 943  
 944      /**
 945       * Opens a grid column
 946       *
 947       * @param int $size The number of segments this column should span.
 948       * @param string $id An id to give the column.
 949       * @param string $class A class to give the column.
 950       * @return string
 951       */
 952      public function grid_column_start($size, $id = null, $class = null) {
 953  
 954          if ($id == 'course-detail') {
 955              $size = 12;
 956              $bootstrapclass = 'col-md-'.$size;
 957          } else {
 958              $bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
 959          }
 960  
 961          $yuigridclass = "col-sm";
 962  
 963          if (is_null($class)) {
 964              $class = $yuigridclass . ' ' . $bootstrapclass;
 965          } else {
 966              $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
 967          }
 968          $attributes = array();
 969          if (!is_null($id)) {
 970              $attributes['id'] = $id;
 971          }
 972          return html_writer::start_div($class . " grid_column_start", $attributes);
 973      }
 974  
 975      /**
 976       * Closes a grid column.
 977       *
 978       * @return string
 979       */
 980      public function grid_column_end() {
 981          return html_writer::end_div();
 982      }
 983  
 984      /**
 985       * Renders an action_icon.
 986       *
 987       * This function uses the {@link core_renderer::action_link()} method for the
 988       * most part. What it does different is prepare the icon as HTML and use it
 989       * as the link text.
 990       *
 991       * @param string|moodle_url $url A string URL or moodel_url
 992       * @param pix_icon $pixicon
 993       * @param component_action $action
 994       * @param array $attributes associative array of html link attributes + disabled
 995       * @param bool $linktext show title next to image in link
 996       * @return string HTML fragment
 997       */
 998      public function action_icon($url, pix_icon $pixicon, component_action $action = null,
 999                                  array $attributes = null, $linktext = false) {
1000          if (!($url instanceof moodle_url)) {
1001              $url = new moodle_url($url);
1002          }
1003          $attributes = (array)$attributes;
1004  
1005          if (empty($attributes['class'])) {
1006              // Let devs override the class via $attributes.
1007              $attributes['class'] = 'action-icon';
1008          }
1009  
1010          $icon = $this->render($pixicon);
1011  
1012          if ($linktext) {
1013              $text = $pixicon->attributes['alt'];
1014          } else {
1015              $text = '';
1016          }
1017  
1018          return $this->action_link($url, $icon.$text, $action, $attributes);
1019      }
1020  
1021      /**
1022       * Displays a view mode selector.
1023       *
1024       * @param array $modes An array of view modes.
1025       * @param string $currentmode The current view mode.
1026       * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
1027       * @param string $param The param name.
1028       * @return string
1029       */
1030      public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
1031          if ($url === null) {
1032              $url = $this->page->url;
1033          }
1034  
1035          $menu = new action_menu;
1036          $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
1037  
1038          $selected = null;
1039          foreach ($modes as $mode => $modestr) {
1040              $attributes = array(
1041                  'class' => 'vms-mode',
1042                  'data-mode' => $mode
1043              );
1044              if ($currentmode === $mode) {
1045                  $attributes['class'] .= ' currentmode';
1046                  $selected = $modestr;
1047              }
1048              if ($selected === null) {
1049                  $selected = $modestr;
1050              }
1051              $modeurl = new moodle_url($url, array($param => $mode));
1052              if ($mode === 'default') {
1053                  $modeurl->remove_params($param);
1054              }
1055              $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
1056          }
1057  
1058          $menu->set_menu_trigger($selected);
1059  
1060          $html = html_writer::start_div('view-mode-selector vms d-flex');
1061          $html .= get_string('viewing').' '.$this->render($menu);
1062          $html .= html_writer::end_div();
1063  
1064          return $html;
1065      }
1066  
1067      /**
1068       * Displays a search result listing.
1069       *
1070       * @param array $courses The courses to display.
1071       * @param int $totalcourses The total number of courses to display.
1072       * @param core_course_list_element $course The currently selected course if there is one.
1073       * @param int $page The current page, starting at 0.
1074       * @param int $perpage The number of courses to display per page.
1075       * @param string $search The string we are searching for.
1076       * @return string
1077       */
1078      public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
1079              $search = '') {
1080          $page = max($page, 0);
1081          $perpage = max($perpage, 2);
1082          $totalpages = ceil($totalcourses / $perpage);
1083          if ($page > $totalpages - 1) {
1084              $page = $totalpages - 1;
1085          }
1086          $courseid = isset($course) ? $course->id : null;
1087          $first = true;
1088          $last = false;
1089          $i = $page * $perpage;
1090  
1091          $html  = html_writer::start_div('course-listing w-100', array(
1092                  'data-category' => 'search',
1093                  'data-page' => $page,
1094                  'data-totalpages' => $totalpages,
1095                  'data-totalcourses' => $totalcourses
1096          ));
1097          $html .= html_writer::tag('h3', get_string('courses'));
1098          $html .= $this->search_pagination($totalcourses, $page, $perpage);
1099          $html .= html_writer::start_tag('ul', array('class' => 'ml'));
1100          foreach ($courses as $listitem) {
1101              $i++;
1102              if ($i == $totalcourses) {
1103                  $last = true;
1104              }
1105              $html .= $this->search_listitem($listitem, $courseid, $first, $last);
1106              $first = false;
1107          }
1108          $html .= html_writer::end_tag('ul');
1109          $html .= $this->search_pagination($totalcourses, $page, $perpage, true, $search);
1110          $html .= $this->course_search_bulk_actions();
1111          $html .= html_writer::end_div();
1112          return $html;
1113      }
1114  
1115      /**
1116       * Displays pagination for search results.
1117       *
1118       * @param int $totalcourses The total number of courses to be displayed.
1119       * @param int $page The current page.
1120       * @param int $perpage The number of courses being displayed.
1121       * @param bool $showtotals Whether or not to print total information.
1122       * @param string $search The string we are searching for.
1123       * @return string
1124       */
1125      protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false, $search = '') {
1126          $html = '';
1127          $totalpages = ceil($totalcourses / $perpage);
1128          if ($showtotals) {
1129              if ($totalpages == 0) {
1130                  $str = get_string('nocoursesfound', 'moodle', s($search));
1131              } else if ($totalpages == 1) {
1132                  $str = get_string('showingacourses', 'moodle', $totalcourses);
1133              } else {
1134                  $a = new stdClass;
1135                  $a->start = ($page * $perpage) + 1;
1136                  $a->end = min((($page + 1) * $perpage), $totalcourses);
1137                  $a->total = $totalcourses;
1138                  $str = get_string('showingxofycourses', 'moodle', $a);
1139              }
1140              $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
1141          }
1142  
1143          if ($totalcourses < $perpage) {
1144              return $html;
1145          }
1146          $aside = 2;
1147          $span = $aside * 2 + 1;
1148          $start = max($page - $aside, 0);
1149          $end = min($page + $aside, $totalpages - 1);
1150          if (($end - $start) < $span) {
1151              if ($start == 0) {
1152                  $end = min($totalpages - 1, $span - 1);
1153              } else if ($end == ($totalpages - 1)) {
1154                  $start = max(0, $end - $span + 1);
1155              }
1156          }
1157          $items = array();
1158          $baseurl = $this->page->url;
1159          if ($page > 0) {
1160              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
1161              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
1162              $items[] = '...';
1163          }
1164          for ($i = $start; $i <= $end; $i++) {
1165              $class = '';
1166              if ($page == $i) {
1167                  $class = 'active-page';
1168              }
1169              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
1170          }
1171          if ($page < ($totalpages - 1)) {
1172              $items[] = '...';
1173              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
1174              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
1175          }
1176  
1177          $html .= html_writer::div(join('', $items), 'listing-pagination');
1178          return $html;
1179      }
1180  
1181      /**
1182       * Renderers a search result course list item.
1183       *
1184       * This function will be called for every course being displayed by course_listing.
1185       *
1186       * @param core_course_list_element $course The course to produce HTML for.
1187       * @param int $selectedcourse The id of the currently selected course.
1188       * @return string
1189       */
1190      public function search_listitem(core_course_list_element $course, $selectedcourse) {
1191  
1192          $text = $course->get_formatted_name();
1193          $attributes = array(
1194                  'class' => 'listitem listitem-course list-group-item list-group-item-action',
1195                  'data-id' => $course->id,
1196                  'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
1197                  'data-visible' => $course->visible ? '1' : '0'
1198          );
1199          $bulkcourseinput = '';
1200          if (core_course_category::get($course->category)->can_move_courses_out_of()) {
1201              $bulkcourseinput = array(
1202                      'type' => 'checkbox',
1203                      'id' => 'coursesearchlistitem' . $course->id,
1204                      'name' => 'bc[]',
1205                      'value' => $course->id,
1206                      'class' => 'bulk-action-checkbox custom-control-input',
1207                      'data-action' => 'select'
1208              );
1209          }
1210          $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
1211          $categoryname = core_course_category::get($course->category)->get_formatted_name();
1212  
1213          $html  = html_writer::start_tag('li', $attributes);
1214          $html .= html_writer::start_div('clearfix');
1215          $html .= html_writer::start_div('float-left');
1216          if ($bulkcourseinput) {
1217              $html .= html_writer::start_div('custom-control custom-checkbox mr-1');
1218              $html .= html_writer::empty_tag('input', $bulkcourseinput);
1219              $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
1220              $html .= html_writer::tag('label', $labeltext, array(
1221                  'class' => 'custom-control-label',
1222                  'for' => 'coursesearchlistitem' . $course->id));
1223              $html .= html_writer::end_div();
1224          }
1225          $html .= html_writer::end_div();
1226          $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
1227          $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left ml-3 text-muted'));
1228          $html .= html_writer::start_div('float-right');
1229          $html .= $this->search_listitem_actions($course);
1230          $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
1231          $html .= html_writer::end_div();
1232          $html .= html_writer::end_div();
1233          $html .= html_writer::end_tag('li');
1234          return $html;
1235      }
1236  
1237      /**
1238       * Renderers actions for individual course actions.
1239       *
1240       * @param core_course_list_element  $course The course to renderer actions for.
1241       * @return string
1242       */
1243      public function search_listitem_actions(core_course_list_element $course) {
1244          $baseurl = new moodle_url(
1245              '/course/managementsearch.php',
1246              array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
1247          );
1248          $actions = array();
1249          // Edit.
1250          if ($course->can_access()) {
1251              if ($course->can_edit()) {
1252                  $actions[] = $this->output->action_icon(
1253                      new moodle_url('/course/edit.php', array('id' => $course->id)),
1254                      new pix_icon('t/edit', get_string('edit')),
1255                      null,
1256                      array('class' => 'action-edit')
1257                  );
1258              }
1259              // Delete.
1260              if ($course->can_delete()) {
1261                  $actions[] = $this->output->action_icon(
1262                      new moodle_url('/course/delete.php', array('id' => $course->id)),
1263                      new pix_icon('t/delete', get_string('delete')),
1264                      null,
1265                      array('class' => 'action-delete')
1266                  );
1267              }
1268              // Show/Hide.
1269              if ($course->can_change_visibility()) {
1270                      $actions[] = $this->output->action_icon(
1271                          new moodle_url($baseurl, array('action' => 'hidecourse')),
1272                          new pix_icon('t/hide', get_string('hide')),
1273                          null,
1274                          array('data-action' => 'hide', 'class' => 'action-hide')
1275                      );
1276                      $actions[] = $this->output->action_icon(
1277                          new moodle_url($baseurl, array('action' => 'showcourse')),
1278                          new pix_icon('t/show', get_string('show')),
1279                          null,
1280                          array('data-action' => 'show', 'class' => 'action-show')
1281                      );
1282              }
1283          }
1284          if (empty($actions)) {
1285              return '';
1286          }
1287          return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1288      }
1289  
1290      /**
1291       * Renders html to display a course search form
1292       *
1293       * @param string $value default value to populate the search field
1294       * @param string $format display format - 'plain' (default), 'short' or 'navbar'
1295       * @return string
1296       */
1297      public function course_search_form($value = '', $format = 'plain') {
1298          static $count = 0;
1299          $formid = 'coursesearch';
1300          if ((++$count) > 1) {
1301              $formid .= $count;
1302          }
1303  
1304          switch ($format) {
1305              case 'navbar' :
1306                  $formid = 'coursesearchnavbar';
1307                  $inputid = 'navsearchbox';
1308                  $inputsize = 20;
1309                  break;
1310              case 'short' :
1311                  $inputid = 'shortsearchbox';
1312                  $inputsize = 12;
1313                  break;
1314              default :
1315                  $inputid = 'coursesearchbox';
1316                  $inputsize = 30;
1317          }
1318  
1319          $strsearchcourses = get_string("searchcourses");
1320          $searchurl = new moodle_url('/course/management.php');
1321  
1322          $output = html_writer::start_div('row');
1323          $output .= html_writer::start_div('col-md-12');
1324          $output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
1325                  'action' => $searchurl, 'method' => 'get'));
1326          $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
1327          $output .= html_writer::tag('legend', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
1328                  array('class' => 'card-header'));
1329          $output .= html_writer::start_div('card-body');
1330          $output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
1331          $output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
1332                  'size' => $inputsize, 'name' => 'search', 'value' => s($value), 'aria-label' => get_string('searchcourses')));
1333          $output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
1334          $output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
1335          $output .= html_writer::end_tag('span');
1336          $output .= html_writer::end_div();
1337          $output .= html_writer::end_div();
1338          $output .= html_writer::end_tag('fieldset');
1339          $output .= html_writer::end_tag('form');
1340          $output .= html_writer::end_div();
1341          $output .= html_writer::end_div();
1342  
1343          return $output;
1344      }
1345  
1346      /**
1347       * Creates access hidden skip to links for the displayed sections.
1348       *
1349       * @param bool $displaycategorylisting
1350       * @param bool $displaycourselisting
1351       * @param bool $displaycoursedetail
1352       * @return string
1353       */
1354      public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
1355          $html = html_writer::start_div('skiplinks accesshide');
1356          $url = new moodle_url($this->page->url);
1357          if ($displaycategorylisting) {
1358              $url->set_anchor('category-listing');
1359              $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
1360          }
1361          if ($displaycourselisting) {
1362              $url->set_anchor('course-listing');
1363              $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
1364          }
1365          if ($displaycoursedetail) {
1366              $url->set_anchor('course-detail');
1367              $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
1368          }
1369          $html .= html_writer::end_div();
1370          return $html;
1371      }
1372  
1373  }