MDL-69454 core_search: consistent course management search
[moodle.git] / course / classes / management_renderer.php
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/>.
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  */
25 defined('MOODLE_INTERNAL') || die;
27 require_once($CFG->dirroot.'/course/renderer.php');
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 {
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     }
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     }
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');
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     }
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     }
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) {
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);
140         $listing = core_course_category::top()->get_children();
142         $attributes = array(
143                 'class' => 'ml-1 list-unstyled',
144                 'role' => 'tree',
145                 'aria-labelledby' => 'category-listing-title'
146         );
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     }
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()) {
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         );
220         $checkboxclass = '';
221         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
222             // Very very hardcoded here.
223             $checkboxclass = 'd-none';
224         }
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();
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     }
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();
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         }
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     }
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     }
384     public function render_action_menu($menu) {
385         return $this->output->render($menu);
386     }
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');
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();
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     }
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') {
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         }
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         }
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     }
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         }
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         }
593         $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
594         return $html;
595     }
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) {
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         );
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         );
626         $checkboxclass = '';
627         if (!$category->has_manage_capability()) {
628             // Very very hardcoded here.
629             $checkboxclass = 'd-none';
630         }
632         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
634         $html  = html_writer::start_tag('li', $attributes);
635         $html .= html_writer::start_div('clearfix');
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         }
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     }
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     }
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     }
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     }
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     }
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'];
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     }
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     }
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     }
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     }
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     }
935     /**
936      * Closes the grid.
937      *
938      * @return string
939      */
940     public function grid_end() {
941         return html_writer::end_div();
942     }
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) {
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         }
961         $yuigridclass = "col-sm";
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     }
975     /**
976      * Closes a grid column.
977      *
978      * @return string
979      */
980     public function grid_column_end() {
981         return html_writer::end_div();
982     }
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;
1005         if (empty($attributes['class'])) {
1006             // Let devs override the class via $attributes.
1007             $attributes['class'] = 'action-icon';
1008         }
1010         $icon = $this->render($pixicon);
1012         if ($linktext) {
1013             $text = $pixicon->attributes['alt'];
1014         } else {
1015             $text = '';
1016         }
1018         return $this->action_link($url, $icon.$text, $action, $attributes);
1019     }
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         }
1035         $menu = new action_menu;
1036         $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
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         }
1058         $menu->set_menu_trigger($selected);
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();
1064         return $html;
1065     }
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;
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     }
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         }
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         }
1177         $html .= html_writer::div(join('', $items), 'listing-pagination');
1178         return $html;
1179     }
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) {
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();
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     }
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     }
1290     /**
1291      * Renders html to display a course search form
1292      *
1293      * @param string $value default value to populate the search field
1294      * @return string
1295      */
1296     public function course_search_form($value = '') {
1298         $data = [
1299             'action' => new moodle_url('/course/management.php'),
1300             'btnclass' => 'btn-primary',
1301             'extraclasses' => 'my-3 d-flex justify-content-center',
1302             'inputname' => 'search',
1303             'searchstring' => get_string('searchcourses'),
1304             'value' => $value
1305         ];
1306         return $this->render_from_template('core/search_input', $data);
1307     }
1309     /**
1310      * Creates access hidden skip to links for the displayed sections.
1311      *
1312      * @param bool $displaycategorylisting
1313      * @param bool $displaycourselisting
1314      * @param bool $displaycoursedetail
1315      * @return string
1316      */
1317     public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
1318         $html = html_writer::start_div('skiplinks accesshide');
1319         $url = new moodle_url($this->page->url);
1320         if ($displaycategorylisting) {
1321             $url->set_anchor('category-listing');
1322             $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
1323         }
1324         if ($displaycourselisting) {
1325             $url->set_anchor('course-listing');
1326             $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
1327         }
1328         if ($displaycoursedetail) {
1329             $url->set_anchor('course-detail');
1330             $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
1331         }
1332         $html .= html_writer::end_div();
1333         return $html;
1334     }