Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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  
     382          // If the action menu has items, add the menubar role to the main element containing it.
     383          $menu->attributes['role'] = 'menubar';
     384  
     385          return $this->render($menu);
     386      }
     387  
     388      public function render_action_menu($menu) {
     389          return $this->output->render($menu);
     390      }
     391  
     392      /**
     393       * Renders bulk actions for categories.
     394       *
     395       * @param core_course_category $category The currently selected category if there is one.
     396       * @return string
     397       */
     398      public function category_bulk_actions(core_course_category $category = null) {
     399          // Resort courses.
     400          // Change parent.
     401          if (!core_course_category::can_resort_any() && !core_course_category::can_change_parent_any()) {
     402              return '';
     403          }
     404          $strgo = new lang_string('go');
     405  
     406          $html  = html_writer::start_div('category-bulk-actions bulk-actions');
     407          $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
     408          if (core_course_category::can_resort_any()) {
     409              $selectoptions = array(
     410                  'selectedcategories' => get_string('selectedcategories'),
     411                  'allcategories' => get_string('allcategories')
     412              );
     413              $form = html_writer::start_div();
     414              if ($category) {
     415                  $selectoptions = array('thiscategory' => get_string('thiscategory')) + $selectoptions;
     416                  $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentcategoryid', 'value' => $category->id));
     417              }
     418              $form .= html_writer::div(
     419                  html_writer::select(
     420                      $selectoptions,
     421                      'selectsortby',
     422                      'selectedcategories',
     423                      false,
     424                      array('aria-label' => get_string('selectcategorysort'))
     425                  )
     426              );
     427              $form .= html_writer::div(
     428                  html_writer::select(
     429                      array(
     430                          'name' => get_string('sortbyx', 'moodle', get_string('categoryname')),
     431                          'namedesc' => get_string('sortbyxreverse', 'moodle', get_string('categoryname')),
     432                          'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercoursecategory')),
     433                          'idnumberdesc' => get_string('sortbyxreverse' , 'moodle' , get_string('idnumbercoursecategory')),
     434                          'none' => get_string('dontsortcategories')
     435                      ),
     436                      'resortcategoriesby',
     437                      'name',
     438                      false,
     439                      array('aria-label' => get_string('selectcategorysortby'), 'class' => 'mt-1')
     440                  )
     441              );
     442              $form .= html_writer::div(
     443                  html_writer::select(
     444                      array(
     445                          'fullname' => get_string('sortbyx', 'moodle', get_string('fullnamecourse')),
     446                          'fullnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse')),
     447                          'shortname' => get_string('sortbyx', 'moodle', get_string('shortnamecourse')),
     448                          'shortnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse')),
     449                          'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercourse')),
     450                          'idnumberdesc' => get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse')),
     451                          'timecreated' => get_string('sortbyx', 'moodle', get_string('timecreatedcourse')),
     452                          'timecreateddesc' => get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')),
     453                          'none' => get_string('dontsortcourses')
     454                      ),
     455                      'resortcoursesby',
     456                      'fullname',
     457                      false,
     458                      array('aria-label' => get_string('selectcoursesortby'), 'class' => 'mt-1')
     459                  )
     460              );
     461              $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort',
     462                  'value' => get_string('sort'), 'class' => 'btn btn-secondary my-1'));
     463              $form .= html_writer::end_div();
     464  
     465              $html .= html_writer::start_div('detail-pair row yui3-g my-1');
     466              $html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key col-md-3 yui3-u-1-4');
     467              $html .= html_writer::div($form, 'pair-value col-md-9 yui3-u-3-4');
     468              $html .= html_writer::end_div();
     469          }
     470          if (core_course_category::can_change_parent_any()) {
     471              $options = array();
     472              if (core_course_category::top()->has_manage_capability()) {
     473                  $options[0] = core_course_category::top()->get_formatted_name();
     474              }
     475              $options += core_course_category::make_categories_list('moodle/category:manage');
     476              $select = html_writer::select(
     477                  $options,
     478                  'movecategoriesto',
     479                  '',
     480                  array('' => 'choosedots'),
     481                  array('aria-labelledby' => 'moveselectedcategoriesto', 'class' => 'mr-1')
     482              );
     483              $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'),
     484                  'class' => 'btn btn-secondary');
     485              $html .= $this->detail_pair(
     486                  html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
     487                  $select . html_writer::empty_tag('input', $submit)
     488              );
     489          }
     490          $html .= html_writer::end_div();
     491          return $html;
     492      }
     493  
     494      /**
     495       * Renders a course listing.
     496       *
     497       * @param core_course_category $category The currently selected category. This is what the listing is focused on.
     498       * @param core_course_list_element $course The currently selected course.
     499       * @param int $page The page being displayed.
     500       * @param int $perpage The number of courses to display per page.
     501       * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
     502       * @return string
     503       */
     504      public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
     505              $page = 0, $perpage = 20, $viewmode = 'default') {
     506  
     507          if ($category === null) {
     508              $html = html_writer::start_div('select-a-category');
     509              $html .= html_writer::tag('h3', get_string('courses'),
     510                      array('id' => 'course-listing-title', 'tabindex' => '0'));
     511              $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
     512              $html .= html_writer::end_div();
     513              return $html;
     514          }
     515  
     516          $page = max($page, 0);
     517          $perpage = max($perpage, 2);
     518          $totalcourses = $category->coursecount;
     519          $totalpages = ceil($totalcourses / $perpage);
     520          if ($page > $totalpages - 1) {
     521              $page = $totalpages - 1;
     522          }
     523          $options = array(
     524                  'offset' => $page * $perpage,
     525                  'limit' => $perpage
     526          );
     527          $courseid = isset($course) ? $course->id : null;
     528          $class = '';
     529          if ($page === 0) {
     530              $class .= ' firstpage';
     531          }
     532          if ($page + 1 === (int)$totalpages) {
     533              $class .= ' lastpage';
     534          }
     535  
     536          $html  = html_writer::start_div('card course-listing w-100'.$class, array(
     537                  'data-category' => $category->id,
     538                  'data-page' => $page,
     539                  'data-totalpages' => $totalpages,
     540                  'data-totalcourses' => $totalcourses,
     541                  'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
     542          ));
     543          $html .= html_writer::tag('h3', $category->get_formatted_name(),
     544                  array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
     545          $html .= html_writer::start_div('card-body');
     546          $html .= $this->course_listing_actions($category, $course, $perpage);
     547          $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
     548          $html .= html_writer::start_tag('ul', array('class' => 'ml course-list'));
     549          foreach ($category->get_courses($options) as $listitem) {
     550              $html .= $this->course_listitem($category, $listitem, $courseid);
     551          }
     552          $html .= html_writer::end_tag('ul');
     553          $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
     554          $html .= $this->course_bulk_actions($category);
     555          $html .= html_writer::end_div();
     556          $html .= html_writer::end_div();
     557          return $html;
     558      }
     559  
     560      /**
     561       * Renders pagination for a course listing.
     562       *
     563       * @param core_course_category $category The category to produce pagination for.
     564       * @param int $page The current page.
     565       * @param int $perpage The number of courses to display per page.
     566       * @param bool $showtotals Set to true to show the total number of courses and what is being displayed.
     567       * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
     568       * @return string
     569       */
     570      protected function listing_pagination(core_course_category $category, $page, $perpage, $showtotals = false,
     571                                            $viewmode = 'default') {
     572          $html = '';
     573          $totalcourses = $category->get_courses_count();
     574          $totalpages = ceil($totalcourses / $perpage);
     575          if ($showtotals) {
     576              if ($totalpages == 0) {
     577                  $str = get_string('nocoursesyet');
     578              } else if ($totalpages == 1) {
     579                  $str = get_string('showingacourses', 'moodle', $totalcourses);
     580              } else {
     581                  $a = new stdClass;
     582                  $a->start = ($page * $perpage) + 1;
     583                  $a->end = min((($page + 1) * $perpage), $totalcourses);
     584                  $a->total = $totalcourses;
     585                  $str = get_string('showingxofycourses', 'moodle', $a);
     586              }
     587              $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
     588          }
     589  
     590          if ($viewmode !== 'default') {
     591              $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id,
     592                  'view' => $viewmode));
     593          } else {
     594              $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
     595          }
     596  
     597          $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
     598          return $html;
     599      }
     600  
     601      /**
     602       * Renderers a course list item.
     603       *
     604       * This function will be called for every course being displayed by course_listing.
     605       *
     606       * @param core_course_category $category The currently selected category and the category the course belongs to.
     607       * @param core_course_list_element $course The course to produce HTML for.
     608       * @param int $selectedcourse The id of the currently selected course.
     609       * @return string
     610       */
     611      public function course_listitem(core_course_category $category, core_course_list_element $course, $selectedcourse) {
     612  
     613          $text = $course->get_formatted_name();
     614          $attributes = array(
     615                  'class' => 'listitem listitem-course list-group-item list-group-item-action',
     616                  'data-id' => $course->id,
     617                  'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
     618                  'data-visible' => $course->visible ? '1' : '0'
     619          );
     620  
     621          $bulkcourseinput = array(
     622                  'id' => 'courselistitem' . $course->id,
     623                  'type' => 'checkbox',
     624                  'name' => 'bc[]',
     625                  'value' => $course->id,
     626                  'class' => 'bulk-action-checkbox custom-control-input',
     627                  'data-action' => 'select'
     628          );
     629  
     630          $checkboxclass = '';
     631          if (!$category->has_manage_capability()) {
     632              // Very very hardcoded here.
     633              $checkboxclass = 'd-none';
     634          }
     635  
     636          $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
     637  
     638          $html  = html_writer::start_tag('li', $attributes);
     639          $html .= html_writer::start_div('clearfix');
     640  
     641          if ($category->can_resort_courses()) {
     642              // In order for dnd to be available the user must be able to resort the category children..
     643              $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
     644          }
     645  
     646          $html .= html_writer::start_div('float-left ' . $checkboxclass);
     647          $html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
     648          $html .= html_writer::empty_tag('input', $bulkcourseinput);
     649          $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
     650          $html .= html_writer::tag('label', $labeltext, array(
     651              'class' => 'custom-control-label',
     652              'for' => 'courselistitem' . $course->id));
     653          $html .= html_writer::end_div();
     654          $html .= html_writer::end_div();
     655          $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
     656          $html .= html_writer::start_div('float-right');
     657          if ($course->idnumber) {
     658              $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
     659          }
     660          $html .= $this->course_listitem_actions($category, $course);
     661          $html .= html_writer::end_div();
     662          $html .= html_writer::end_div();
     663          $html .= html_writer::end_tag('li');
     664          return $html;
     665      }
     666  
     667      /**
     668       * Renderers actions for the course listing.
     669       *
     670       * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
     671       *
     672       * @param core_course_category $category
     673       * @param core_course_list_element $course The currently selected course.
     674       * @param int $perpage
     675       * @return string
     676       */
     677      public function course_listing_actions(core_course_category $category, core_course_list_element $course = null, $perpage = 20) {
     678          $actions = array();
     679          if ($category->can_create_course()) {
     680              $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
     681              $actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-secondary'));
     682          }
     683          if ($category->can_request_course()) {
     684              // Request a new course.
     685              $url = new moodle_url('/course/request.php', array('category' => $category->id, 'return' => 'management'));
     686              $actions[] = html_writer::link($url, get_string('requestcourse'));
     687          }
     688          if ($category->can_resort_courses()) {
     689              $params = $this->page->url->params();
     690              $params['action'] = 'resortcourses';
     691              $params['sesskey'] = sesskey();
     692              $baseurl = new moodle_url('/course/management.php', $params);
     693              $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
     694              $fullnameurldesc = new moodle_url($baseurl, array('resort' => 'fullnamedesc'));
     695              $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
     696              $shortnameurldesc = new moodle_url($baseurl, array('resort' => 'shortnamedesc'));
     697              $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
     698              $idnumberdescurl = new moodle_url($baseurl, array('resort' => 'idnumberdesc'));
     699              $timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
     700              $timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
     701              $menu = new action_menu(array(
     702                      new action_menu_link_secondary($fullnameurl,
     703                              null,
     704                              get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
     705                      new action_menu_link_secondary($fullnameurldesc,
     706                              null,
     707                              get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
     708                      new action_menu_link_secondary($shortnameurl,
     709                              null,
     710                              get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
     711                      new action_menu_link_secondary($shortnameurldesc,
     712                              null,
     713                              get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
     714                      new action_menu_link_secondary($idnumberurl,
     715                              null,
     716                              get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
     717                      new action_menu_link_secondary($idnumberdescurl,
     718                              null,
     719                              get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
     720                      new action_menu_link_secondary($timecreatedurl,
     721                              null,
     722                              get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
     723                      new action_menu_link_secondary($timecreateddescurl,
     724                              null,
     725                              get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
     726              ));
     727              $menu->set_menu_trigger(get_string('resortcourses'));
     728              $actions[] = $this->render($menu);
     729          }
     730          $strall = get_string('all');
     731          $menu = new action_menu(array(
     732                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
     733                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
     734                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
     735                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
     736                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
     737                  new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
     738          ));
     739          if ((int)$perpage === 999) {
     740              $perpage = $strall;
     741          }
     742          $menu->attributes['class'] .= ' courses-per-page';
     743          $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
     744          $actions[] = $this->render($menu);
     745          return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
     746      }
     747  
     748      /**
     749       * Renderers actions for individual course actions.
     750       *
     751       * @param core_course_category $category The currently selected category.
     752       * @param core_course_list_element  $course The course to renderer actions for.
     753       * @return string
     754       */
     755      public function course_listitem_actions(core_course_category $category, core_course_list_element $course) {
     756          $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
     757          if (empty($actions)) {
     758              return '';
     759          }
     760          $actionshtml = array();
     761          foreach ($actions as $action) {
     762              $action['attributes']['role'] = 'button';
     763              $actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
     764          }
     765          return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
     766      }
     767  
     768      /**
     769       * Renderers bulk actions that can be performed on courses.
     770       *
     771       * @param core_course_category $category The currently selected category and the category in which courses that
     772       *      are selectable belong.
     773       * @return string
     774       */
     775      public function course_bulk_actions(core_course_category $category) {
     776          $html  = html_writer::start_div('course-bulk-actions bulk-actions');
     777          if ($category->can_move_courses_out_of()) {
     778              $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
     779              $options = core_course_category::make_categories_list('moodle/category:manage');
     780              $select = html_writer::select(
     781                  $options,
     782                  'movecoursesto',
     783                  '',
     784                  array('' => 'choosedots'),
     785                  array('aria-labelledby' => 'moveselectedcoursesto', 'class' => 'mr-1')
     786              );
     787              $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
     788                  'class' => 'btn btn-secondary');
     789              $html .= $this->detail_pair(
     790                  html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
     791                  $select . html_writer::empty_tag('input', $submit)
     792              );
     793          }
     794          $html .= html_writer::end_div();
     795          return $html;
     796      }
     797  
     798      /**
     799       * Renderers bulk actions that can be performed on courses in search returns
     800       *
     801       * @return string
     802       */
     803      public function course_search_bulk_actions() {
     804          $html  = html_writer::start_div('course-bulk-actions bulk-actions');
     805          $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
     806          $options = core_course_category::make_categories_list('moodle/category:manage');
     807          $select = html_writer::select(
     808              $options,
     809              'movecoursesto',
     810              '',
     811              array('' => 'choosedots'),
     812              array('aria-labelledby' => 'moveselectedcoursesto')
     813          );
     814          $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
     815              'class' => 'btn btn-secondary');
     816          $html .= $this->detail_pair(
     817              html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
     818              $select . html_writer::empty_tag('input', $submit)
     819          );
     820          $html .= html_writer::end_div();
     821          return $html;
     822      }
     823  
     824      /**
     825       * Renderers detailed course information.
     826       *
     827       * @param core_course_list_element $course The course to display details for.
     828       * @return string
     829       */
     830      public function course_detail(core_course_list_element $course) {
     831          $details = \core_course\management\helper::get_course_detail_array($course);
     832          $fullname = $details['fullname']['value'];
     833  
     834          $html = html_writer::start_div('course-detail card');
     835          $html .= html_writer::start_div('card-header');
     836          $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title',
     837                  'class' => 'card-title', 'tabindex' => '0'));
     838          $html .= html_writer::end_div();
     839          $html .= html_writer::start_div('card-body');
     840          $html .= $this->course_detail_actions($course);
     841          foreach ($details as $class => $data) {
     842              $html .= $this->detail_pair($data['key'], $data['value'], $class);
     843          }
     844          $html .= html_writer::end_div();
     845          $html .= html_writer::end_div();
     846          return $html;
     847      }
     848  
     849      /**
     850       * Renderers a key value pair of information for display.
     851       *
     852       * @param string $key
     853       * @param string $value
     854       * @param string $class
     855       * @return string
     856       */
     857      protected function detail_pair($key, $value, $class ='') {
     858          $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
     859          $html .= html_writer::div(html_writer::span($key), 'pair-key col-md-3 yui3-u-1-4 font-weight-bold');
     860          $html .= html_writer::div(html_writer::span($value), 'pair-value col-md-8 yui3-u-3-4');
     861          $html .= html_writer::end_div();
     862          return $html;
     863      }
     864  
     865      /**
     866       * A collection of actions for a course.
     867       *
     868       * @param core_course_list_element $course The course to display actions for.
     869       * @return string
     870       */
     871      public function course_detail_actions(core_course_list_element $course) {
     872          $actions = \core_course\management\helper::get_course_detail_actions($course);
     873          if (empty($actions)) {
     874              return '';
     875          }
     876          $options = array();
     877          foreach ($actions as $action) {
     878              $options[] = $this->action_link($action['url'], $action['string'], null,
     879                      array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
     880          }
     881          return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
     882      }
     883  
     884      /**
     885       * Creates an action button (styled link)
     886       *
     887       * @param moodle_url $url The URL to go to when clicked.
     888       * @param string $text The text for the button.
     889       * @param string $id An id to give the button.
     890       * @param string $class A class to give the button.
     891       * @param array $attributes Any additional attributes
     892       * @return string
     893       */
     894      protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
     895          if (isset($attributes['class'])) {
     896              $attributes['class'] .= ' yui3-button';
     897          } else {
     898              $attributes['class'] = 'yui3-button';
     899          }
     900          if (!is_null($id)) {
     901              $attributes['id'] = $id;
     902          }
     903          if (!is_null($class)) {
     904              $attributes['class'] .= ' '.$class;
     905          }
     906          if (is_null($title)) {
     907              $title = $text;
     908          }
     909          $attributes['title'] = $title;
     910          if (!isset($attributes['role'])) {
     911              $attributes['role'] = 'button';
     912          }
     913          return html_writer::link($url, $text, $attributes);
     914      }
     915  
     916      /**
     917       * Opens a grid.
     918       *
     919       * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
     920       *
     921       * @param string $id An id to give this grid.
     922       * @param string $class A class to give this grid.
     923       * @return string
     924       */
     925      public function grid_start($id = null, $class = null) {
     926          $gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
     927          if (is_null($class)) {
     928              $class = $gridclass;
     929          } else {
     930              $class .= ' ' . $gridclass;
     931          }
     932          $attributes = array();
     933          if (!is_null($id)) {
     934              $attributes['id'] = $id;
     935          }
     936          return html_writer::start_div($class, $attributes);
     937      }
     938  
     939      /**
     940       * Closes the grid.
     941       *
     942       * @return string
     943       */
     944      public function grid_end() {
     945          return html_writer::end_div();
     946      }
     947  
     948      /**
     949       * Opens a grid column
     950       *
     951       * @param int $size The number of segments this column should span.
     952       * @param string $id An id to give the column.
     953       * @param string $class A class to give the column.
     954       * @return string
     955       */
     956      public function grid_column_start($size, $id = null, $class = null) {
     957  
     958          if ($id == 'course-detail') {
     959              $size = 12;
     960              $bootstrapclass = 'col-md-'.$size;
     961          } else {
     962              $bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
     963          }
     964  
     965          $yuigridclass = "col-sm";
     966  
     967          if (is_null($class)) {
     968              $class = $yuigridclass . ' ' . $bootstrapclass;
     969          } else {
     970              $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
     971          }
     972          $attributes = array();
     973          if (!is_null($id)) {
     974              $attributes['id'] = $id;
     975          }
     976          return html_writer::start_div($class . " grid_column_start", $attributes);
     977      }
     978  
     979      /**
     980       * Closes a grid column.
     981       *
     982       * @return string
     983       */
     984      public function grid_column_end() {
     985          return html_writer::end_div();
     986      }
     987  
     988      /**
     989       * Renders an action_icon.
     990       *
     991       * This function uses the {@link core_renderer::action_link()} method for the
     992       * most part. What it does different is prepare the icon as HTML and use it
     993       * as the link text.
     994       *
     995       * @param string|moodle_url $url A string URL or moodel_url
     996       * @param pix_icon $pixicon
     997       * @param component_action $action
     998       * @param array $attributes associative array of html link attributes + disabled
     999       * @param bool $linktext show title next to image in link
    1000       * @return string HTML fragment
    1001       */
    1002      public function action_icon($url, pix_icon $pixicon, component_action $action = null,
    1003                                  array $attributes = null, $linktext = false) {
    1004          if (!($url instanceof moodle_url)) {
    1005              $url = new moodle_url($url);
    1006          }
    1007          $attributes = (array)$attributes;
    1008  
    1009          if (empty($attributes['class'])) {
    1010              // Let devs override the class via $attributes.
    1011              $attributes['class'] = 'action-icon';
    1012          }
    1013  
    1014          $icon = $this->render($pixicon);
    1015  
    1016          if ($linktext) {
    1017              $text = $pixicon->attributes['alt'];
    1018          } else {
    1019              $text = '';
    1020          }
    1021  
    1022          return $this->action_link($url, $icon.$text, $action, $attributes);
    1023      }
    1024  
    1025      /**
    1026       * Displays a view mode selector.
    1027       *
    1028       * @param array $modes An array of view modes.
    1029       * @param string $currentmode The current view mode.
    1030       * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
    1031       * @param string $param The param name.
    1032       * @return string
    1033       */
    1034      public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
    1035          if ($url === null) {
    1036              $url = $this->page->url;
    1037          }
    1038  
    1039          $menu = new action_menu;
    1040          $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
    1041  
    1042          $selected = null;
    1043          foreach ($modes as $mode => $modestr) {
    1044              $attributes = array(
    1045                  'class' => 'vms-mode',
    1046                  'data-mode' => $mode
    1047              );
    1048              if ($currentmode === $mode) {
    1049                  $attributes['class'] .= ' currentmode';
    1050                  $selected = $modestr;
    1051              }
    1052              if ($selected === null) {
    1053                  $selected = $modestr;
    1054              }
    1055              $modeurl = new moodle_url($url, array($param => $mode));
    1056              if ($mode === 'default') {
    1057                  $modeurl->remove_params($param);
    1058              }
    1059              $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
    1060          }
    1061  
    1062          $menu->set_menu_trigger($selected);
    1063  
    1064          $html = html_writer::start_div('view-mode-selector vms d-flex');
    1065          $html .= get_string('viewing').' '.$this->render($menu);
    1066          $html .= html_writer::end_div();
    1067  
    1068          return $html;
    1069      }
    1070  
    1071      /**
    1072       * Displays a search result listing.
    1073       *
    1074       * @param array $courses The courses to display.
    1075       * @param int $totalcourses The total number of courses to display.
    1076       * @param core_course_list_element $course The currently selected course if there is one.
    1077       * @param int $page The current page, starting at 0.
    1078       * @param int $perpage The number of courses to display per page.
    1079       * @param string $search The string we are searching for.
    1080       * @return string
    1081       */
    1082      public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
    1083              $search = '') {
    1084          $page = max($page, 0);
    1085          $perpage = max($perpage, 2);
    1086          $totalpages = ceil($totalcourses / $perpage);
    1087          if ($page > $totalpages - 1) {
    1088              $page = $totalpages - 1;
    1089          }
    1090          $courseid = isset($course) ? $course->id : null;
    1091          $first = true;
    1092          $last = false;
    1093          $i = $page * $perpage;
    1094  
    1095          $html  = html_writer::start_div('course-listing w-100', array(
    1096                  'data-category' => 'search',
    1097                  'data-page' => $page,
    1098                  'data-totalpages' => $totalpages,
    1099                  'data-totalcourses' => $totalcourses
    1100          ));
    1101          $html .= html_writer::tag('h3', get_string('courses'));
    1102          $html .= $this->search_pagination($totalcourses, $page, $perpage);
    1103          $html .= html_writer::start_tag('ul', array('class' => 'ml'));
    1104          foreach ($courses as $listitem) {
    1105              $i++;
    1106              if ($i == $totalcourses) {
    1107                  $last = true;
    1108              }
    1109              $html .= $this->search_listitem($listitem, $courseid, $first, $last);
    1110              $first = false;
    1111          }
    1112          $html .= html_writer::end_tag('ul');
    1113          $html .= $this->search_pagination($totalcourses, $page, $perpage, true, $search);
    1114          $html .= $this->course_search_bulk_actions();
    1115          $html .= html_writer::end_div();
    1116          return $html;
    1117      }
    1118  
    1119      /**
    1120       * Displays pagination for search results.
    1121       *
    1122       * @param int $totalcourses The total number of courses to be displayed.
    1123       * @param int $page The current page.
    1124       * @param int $perpage The number of courses being displayed.
    1125       * @param bool $showtotals Whether or not to print total information.
    1126       * @param string $search The string we are searching for.
    1127       * @return string
    1128       */
    1129      protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false, $search = '') {
    1130          $html = '';
    1131          $totalpages = ceil($totalcourses / $perpage);
    1132          if ($showtotals) {
    1133              if ($totalpages == 0) {
    1134                  $str = get_string('nocoursesfound', 'moodle', s($search));
    1135              } else if ($totalpages == 1) {
    1136                  $str = get_string('showingacourses', 'moodle', $totalcourses);
    1137              } else {
    1138                  $a = new stdClass;
    1139                  $a->start = ($page * $perpage) + 1;
    1140                  $a->end = min((($page + 1) * $perpage), $totalcourses);
    1141                  $a->total = $totalcourses;
    1142                  $str = get_string('showingxofycourses', 'moodle', $a);
    1143              }
    1144              $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
    1145          }
    1146  
    1147          if ($totalcourses < $perpage) {
    1148              return $html;
    1149          }
    1150          $aside = 2;
    1151          $span = $aside * 2 + 1;
    1152          $start = max($page - $aside, 0);
    1153          $end = min($page + $aside, $totalpages - 1);
    1154          if (($end - $start) < $span) {
    1155              if ($start == 0) {
    1156                  $end = min($totalpages - 1, $span - 1);
    1157              } else if ($end == ($totalpages - 1)) {
    1158                  $start = max(0, $end - $span + 1);
    1159              }
    1160          }
    1161          $items = array();
    1162          $baseurl = $this->page->url;
    1163          if ($page > 0) {
    1164              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
    1165              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
    1166              $items[] = '...';
    1167          }
    1168          for ($i = $start; $i <= $end; $i++) {
    1169              $class = '';
    1170              if ($page == $i) {
    1171                  $class = 'active-page';
    1172              }
    1173              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
    1174          }
    1175          if ($page < ($totalpages - 1)) {
    1176              $items[] = '...';
    1177              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
    1178              $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
    1179          }
    1180  
    1181          $html .= html_writer::div(join('', $items), 'listing-pagination');
    1182          return $html;
    1183      }
    1184  
    1185      /**
    1186       * Renderers a search result course list item.
    1187       *
    1188       * This function will be called for every course being displayed by course_listing.
    1189       *
    1190       * @param core_course_list_element $course The course to produce HTML for.
    1191       * @param int $selectedcourse The id of the currently selected course.
    1192       * @return string
    1193       */
    1194      public function search_listitem(core_course_list_element $course, $selectedcourse) {
    1195  
    1196          $text = $course->get_formatted_name();
    1197          $attributes = array(
    1198                  'class' => 'listitem listitem-course list-group-item list-group-item-action',
    1199                  'data-id' => $course->id,
    1200                  'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
    1201                  'data-visible' => $course->visible ? '1' : '0'
    1202          );
    1203          $bulkcourseinput = '';
    1204          if (core_course_category::get($course->category)->can_move_courses_out_of()) {
    1205              $bulkcourseinput = array(
    1206                      'type' => 'checkbox',
    1207                      'id' => 'coursesearchlistitem' . $course->id,
    1208                      'name' => 'bc[]',
    1209                      'value' => $course->id,
    1210                      'class' => 'bulk-action-checkbox custom-control-input',
    1211                      'data-action' => 'select'
    1212              );
    1213          }
    1214          $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
    1215          $categoryname = core_course_category::get($course->category)->get_formatted_name();
    1216  
    1217          $html  = html_writer::start_tag('li', $attributes);
    1218          $html .= html_writer::start_div('clearfix');
    1219          $html .= html_writer::start_div('float-left');
    1220          if ($bulkcourseinput) {
    1221              $html .= html_writer::start_div('custom-control custom-checkbox mr-1');
    1222              $html .= html_writer::empty_tag('input', $bulkcourseinput);
    1223              $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
    1224              $html .= html_writer::tag('label', $labeltext, array(
    1225                  'class' => 'custom-control-label',
    1226                  'for' => 'coursesearchlistitem' . $course->id));
    1227              $html .= html_writer::end_div();
    1228          }
    1229          $html .= html_writer::end_div();
    1230          $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
    1231          $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left ml-3 text-muted'));
    1232          $html .= html_writer::start_div('float-right');
    1233          $html .= $this->search_listitem_actions($course);
    1234          $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
    1235          $html .= html_writer::end_div();
    1236          $html .= html_writer::end_div();
    1237          $html .= html_writer::end_tag('li');
    1238          return $html;
    1239      }
    1240  
    1241      /**
    1242       * Renderers actions for individual course actions.
    1243       *
    1244       * @param core_course_list_element  $course The course to renderer actions for.
    1245       * @return string
    1246       */
    1247      public function search_listitem_actions(core_course_list_element $course) {
    1248          $baseurl = new moodle_url(
    1249              '/course/managementsearch.php',
    1250              array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
    1251          );
    1252          $actions = array();
    1253          // Edit.
    1254          if ($course->can_access()) {
    1255              if ($course->can_edit()) {
    1256                  $actions[] = $this->output->action_icon(
    1257                      new moodle_url('/course/edit.php', array('id' => $course->id)),
    1258                      new pix_icon('t/edit', get_string('edit')),
    1259                      null,
    1260                      array('class' => 'action-edit')
    1261                  );
    1262              }
    1263              // Delete.
    1264              if ($course->can_delete()) {
    1265                  $actions[] = $this->output->action_icon(
    1266                      new moodle_url('/course/delete.php', array('id' => $course->id)),
    1267                      new pix_icon('t/delete', get_string('delete')),
    1268                      null,
    1269                      array('class' => 'action-delete')
    1270                  );
    1271              }
    1272              // Show/Hide.
    1273              if ($course->can_change_visibility()) {
    1274                      $actions[] = $this->output->action_icon(
    1275                          new moodle_url($baseurl, array('action' => 'hidecourse')),
    1276                          new pix_icon('t/hide', get_string('hide')),
    1277                          null,
    1278                          array('data-action' => 'hide', 'class' => 'action-hide')
    1279                      );
    1280                      $actions[] = $this->output->action_icon(
    1281                          new moodle_url($baseurl, array('action' => 'showcourse')),
    1282                          new pix_icon('t/show', get_string('show')),
    1283                          null,
    1284                          array('data-action' => 'show', 'class' => 'action-show')
    1285                      );
    1286              }
    1287          }
    1288          if (empty($actions)) {
    1289              return '';
    1290          }
    1291          return html_writer::span(join('', $actions), 'course-item-actions item-actions');
    1292      }
    1293  
    1294      /**
    1295       * Renders html to display a course search form
    1296       *
    1297       * @param string $value default value to populate the search field
    1298       * @return string
    1299       */
    1300      public function course_search_form($value = '') {
    1301  
    1302          $data = [
    1303              'action' => new moodle_url('/course/management.php'),
    1304              'btnclass' => 'btn-primary',
    1305              'extraclasses' => 'my-3 d-flex justify-content-center',
    1306              'inputname' => 'search',
    1307              'searchstring' => get_string('searchcourses'),
    1308              'value' => $value
    1309          ];
    1310          return $this->render_from_template('core/search_input', $data);
    1311      }
    1312  
    1313      /**
    1314       * Creates access hidden skip to links for the displayed sections.
    1315       *
    1316       * @param bool $displaycategorylisting
    1317       * @param bool $displaycourselisting
    1318       * @param bool $displaycoursedetail
    1319       * @return string
    1320       */
    1321      public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
    1322          $html = html_writer::start_div('skiplinks accesshide');
    1323          $url = new moodle_url($this->page->url);
    1324          if ($displaycategorylisting) {
    1325              $url->set_anchor('category-listing');
    1326              $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
    1327          }
    1328          if ($displaycourselisting) {
    1329              $url->set_anchor('course-listing');
    1330              $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
    1331          }
    1332          if ($displaycoursedetail) {
    1333              $url->set_anchor('course-detail');
    1334              $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
    1335          }
    1336          $html .= html_writer::end_div();
    1337          return $html;
    1338      }
    1339  
    1340  }