443764c6cf5a82c52eb4efac2541f1c1416ad2f3
[moodle.git] / theme / boost / classes / output / core_course / management / renderer.php
1 <?php
2 // This file is part of The Bootstrap Moodle theme
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  * Renderers to align Moodle's HTML with that expected by Bootstrap
19  *
20  * @package    theme_boost
21  * @copyright   2018 Bas Brands
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace theme_boost\output\core_course\management;
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->dirroot . "/course/classes/management_renderer.php");
30 use html_writer;
31 use core_course_category;
32 use moodle_url;
33 use core_course_list_element;
34 use lang_string;
35 use context_system;
36 use stdClass;
37 use action_menu;
38 use action_menu_link_secondary;
40 /**
41  * Main renderer for the course management pages.
42  *
43  * @package theme_boost
44  * @copyright 2013 Sam Hemelryk
45  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class renderer extends \core_course_management_renderer {
49     /**
50      * Opens a grid.
51      *
52      * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
53      *
54      * @param string $id An id to give this grid.
55      * @param string $class A class to give this grid.
56      * @return string
57      */
58     public function grid_start($id = null, $class = null) {
59         $gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
60         if (is_null($class)) {
61             $class = $gridclass;
62         } else {
63             $class .= ' ' . $gridclass;
64         }
65         $attributes = array();
66         if (!is_null($id)) {
67             $attributes['id'] = $id;
68         }
69         return html_writer::start_div($class, $attributes);
70     }
72     /**
73      * Opens a grid column
74      *
75      * @param int $size The number of segments this column should span.
76      * @param string $id An id to give the column.
77      * @param string $class A class to give the column.
78      * @return string
79      */
80     public function grid_column_start($size, $id = null, $class = null) {
82         if ($id == 'course-detail') {
83             $size = 12;
84             $bootstrapclass = 'col-md-'.$size;
85         } else {
86             $bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
87         }
89         $yuigridclass = "col-sm";
91         if (is_null($class)) {
92             $class = $yuigridclass . ' ' . $bootstrapclass;
93         } else {
94             $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
95         }
96         $attributes = array();
97         if (!is_null($id)) {
98             $attributes['id'] = $id;
99         }
100         return html_writer::start_div($class . " grid_column_start", $attributes);
101     }
103     /**
104      * Renderers detailed course information.
105      *
106      * @param core_course_list_element $course The course to display details for.
107      * @return string
108      */
109     public function course_detail(core_course_list_element $course) {
110         $details = \core_course\management\helper::get_course_detail_array($course);
111         $fullname = $details['fullname']['value'];
113         $html = html_writer::start_div('course-detail card');
114         $html .= html_writer::start_div('card-header');
115         $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title',
116             'class' => 'card-title', 'tabindex' => '0'));
117         $html .= html_writer::end_div();
118         $html .= html_writer::start_div('card-body');
119         $html .= $this->course_detail_actions($course);
120         foreach ($details as $class => $data) {
121             $html .= $this->detail_pair($data['key'], $data['value'], $class);
122         }
123         $html .= html_writer::end_div();
124         $html .= html_writer::end_div();
125         return $html;
126     }
128     /**
129      * Renders html to display a course search form
130      *
131      * @param string $value default value to populate the search field
132      * @param string $format display format - 'plain' (default), 'short' or 'navbar'
133      * @return string
134      */
135     public function course_search_form($value = '', $format = 'plain') {
136         static $count = 0;
137         $formid = 'coursesearch';
138         if ((++$count) > 1) {
139             $formid .= $count;
140         }
142         switch ($format) {
143             case 'navbar' :
144                 $formid = 'coursesearchnavbar';
145                 $inputid = 'navsearchbox';
146                 $inputsize = 20;
147                 break;
148             case 'short' :
149                 $inputid = 'shortsearchbox';
150                 $inputsize = 12;
151                 break;
152             default :
153                 $inputid = 'coursesearchbox';
154                 $inputsize = 30;
155         }
157         $strsearchcourses = get_string("searchcourses");
158         $searchurl = new moodle_url('/course/management.php');
160         $output = html_writer::start_div('row');
161         $output .= html_writer::start_div('col-md-12');
162         $output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
163             'action' => $searchurl, 'method' => 'get'));
164         $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
165         $output .= html_writer::tag('div', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
166             array('class' => 'card-header'));
167         $output .= html_writer::start_div('card-body');
168         $output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
169         $output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
170             'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
171         $output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
172         $output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
173         $output .= html_writer::end_tag('span');
174         $output .= html_writer::end_div();
175         $output .= html_writer::end_div();
176         $output .= html_writer::end_tag('fieldset');
177         $output .= html_writer::end_tag('form');
178         $output .= html_writer::end_div();
179         $output .= html_writer::end_div();
181         return $output;
182     }
184     /**
185      * Presents a course category listing.
186      *
187      * @param core_course_category $category The currently selected category. Also the category to highlight in the listing.
188      * @return string
189      */
190     public function category_listing(core_course_category $category = null) {
192         if ($category === null) {
193             $selectedparents = array();
194             $selectedcategory = null;
195         } else {
196             $selectedparents = $category->get_parents();
197             $selectedparents[] = $category->id;
198             $selectedcategory = $category->id;
199         }
200         $catatlevel = \core_course\management\helper::get_expanded_categories('');
201         $catatlevel[] = array_shift($selectedparents);
202         $catatlevel = array_unique($catatlevel);
204         $listing = core_course_category::get(0)->get_children();
206         $attributes = array(
207             'class' => 'ml-1 list-unstyled',
208             'role' => 'tree',
209             'aria-labelledby' => 'category-listing-title'
210         );
212         $html  = html_writer::start_div('category-listing card w-100');
213         $html .= html_writer::tag('h3', get_string('categories'),
214             array('class' => 'card-header', 'id' => 'category-listing-title'));
215         $html .= html_writer::start_div('card-body');
216         $html .= $this->category_listing_actions($category);
217         $html .= html_writer::start_tag('ul', $attributes);
218         foreach ($listing as $listitem) {
219             // Render each category in the listing.
220             $subcategories = array();
221             if (in_array($listitem->id, $catatlevel)) {
222                 $subcategories = $listitem->get_children();
223             }
224             $html .= $this->category_listitem(
225                 $listitem,
226                 $subcategories,
227                 $listitem->get_children_count(),
228                 $selectedcategory,
229                 $selectedparents
230             );
231         }
232         $html .= html_writer::end_tag('ul');
233         $html .= $this->category_bulk_actions($category);
234         $html .= html_writer::end_div();
235         $html .= html_writer::end_div();
236         return $html;
237     }
239     /**
240      * Renders a category list item.
241      *
242      * This function gets called recursively to render sub categories.
243      *
244      * @param core_course_category $category The category to render as listitem.
245      * @param core_course_category[] $subcategories The subcategories belonging to the category being rented.
246      * @param int $totalsubcategories The total number of sub categories.
247      * @param int $selectedcategory The currently selected category
248      * @param int[] $selectedcategories The path to the selected category and its ID.
249      * @return string
250      */
251     public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
252                                       $selectedcategory = null, $selectedcategories = array()) {
254         $isexpandable = ($totalsubcategories > 0);
255         $isexpanded = (!empty($subcategories));
256         $activecategory = ($selectedcategory === $category->id);
257         $attributes = array(
258             'class' => 'listitem listitem-category list-group-item list-group-item-action',
259             'data-id' => $category->id,
260             'data-expandable' => $isexpandable ? '1' : '0',
261             'data-expanded' => $isexpanded ? '1' : '0',
262             'data-selected' => $activecategory ? '1' : '0',
263             'data-visible' => $category->visible ? '1' : '0',
264             'role' => 'treeitem',
265             'aria-expanded' => $isexpanded ? 'true' : 'false'
266         );
267         $text = $category->get_formatted_name();
268         if ($category->parent) {
269             $a = new stdClass;
270             $a->category = $text;
271             $a->parentcategory = $category->get_parent_coursecat()->get_formatted_name();
272             $textlabel = get_string('categorysubcategoryof', 'moodle', $a);
273         }
274         $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
275         $bcatinput = array(
276             'type' => 'checkbox',
277             'name' => 'bcat[]',
278             'value' => $category->id,
279             'class' => 'bulk-action-checkbox',
280             'aria-label' => get_string('bulkactionselect', 'moodle', $text),
281             'data-action' => 'select'
282         );
284         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
285             // Very very hardcoded here.
286             $bcatinput['style'] = 'visibility:hidden';
287         }
289         $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
290         if ($isexpanded) {
291             $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'),
292                 'moodle', array('class' => 'tree-icon', 'title' => ''));
293             $icon = html_writer::link(
294                 $viewcaturl,
295                 $icon,
296                 array(
297                     'class' => 'float-left',
298                     'data-action' => 'collapse',
299                     'title' => get_string('collapsecategory', 'moodle', $text),
300                     'aria-controls' => 'subcategoryof'.$category->id
301                 )
302             );
303         } else if ($isexpandable) {
304             $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'),
305                 'moodle', array('class' => 'tree-icon', 'title' => ''));
306             $icon = html_writer::link(
307                 $viewcaturl,
308                 $icon,
309                 array(
310                     'class' => 'float-left',
311                     'data-action' => 'expand',
312                     'title' => get_string('expandcategory', 'moodle', $text)
313                 )
314             );
315         } else {
316             $icon = $this->output->pix_icon(
317                 'i/empty',
318                 '',
319                 'moodle',
320                 array('class' => 'tree-icon'));
321             $icon = html_writer::span($icon, 'float-left');
322         }
323         $actions = \core_course\management\helper::get_category_listitem_actions($category);
324         $hasactions = !empty($actions) || $category->can_create_course();
326         $html = html_writer::start_tag('li', $attributes);
327         $html .= html_writer::start_div('clearfix');
328         $html .= html_writer::start_div('float-left ba-checkbox');
329         $html .= html_writer::empty_tag('input', $bcatinput).'&nbsp;';
330         $html .= html_writer::end_div();
331         $html .= $icon;
332         if ($hasactions) {
333             $textattributes = array('class' => 'float-left categoryname');
334         } else {
335             $textattributes = array('class' => 'float-left categoryname without-actions');
336         }
337         if (isset($textlabel)) {
338             $textattributes['aria-label'] = $textlabel;
339         }
340         $html .= html_writer::link($viewcaturl, $text, $textattributes);
341         $html .= html_writer::start_div('float-right d-flex');
342         if ($category->idnumber) {
343             $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
344         }
345         if ($hasactions) {
346             $html .= $this->category_listitem_actions($category, $actions);
347         }
348         $countid = 'course-count-'.$category->id;
349         $html .= html_writer::span(
350             html_writer::span($category->get_courses_count()) .
351             html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
352             $courseicon,
353             'course-count dimmed',
354             array('aria-labelledby' => $countid)
355         );
356         $html .= html_writer::end_div();
357         $html .= html_writer::end_div();
358         if ($isexpanded) {
359             $html .= html_writer::start_tag('ul',
360                 array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
361             $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
362             $catatlevel[] = array_shift($selectedcategories);
363             $catatlevel = array_unique($catatlevel);
364             foreach ($subcategories as $listitem) {
365                 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
366                 $html .= $this->category_listitem(
367                     $listitem,
368                     $childcategories,
369                     $listitem->get_children_count(),
370                     $selectedcategory,
371                     $selectedcategories
372                 );
373             }
374             $html .= html_writer::end_tag('ul');
375         }
376         $html .= html_writer::end_tag('li');
377         return $html;
378     }
380     /**
381      * Renderers the actions that are possible for the course category listing.
382      *
383      * These are not the actions associated with an individual category listing.
384      * That happens through category_listitem_actions.
385      *
386      * @param core_course_category $category
387      * @return string
388      */
389     public function category_listing_actions(core_course_category $category = null) {
390         $actions = array();
392         $cancreatecategory = $category && $category->can_create_subcategory();
393         $cancreatecategory = $cancreatecategory || core_course_category::can_create_top_level_category();
394         if ($category === null) {
395             $category = core_course_category::get(0);
396         }
398         if ($cancreatecategory) {
399             $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
400             $actions[] = html_writer::link($url, get_string('createnewcategory'), array('class' => 'btn btn-default'));
401         }
402         if (core_course_category::can_approve_course_requests()) {
403             $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
404         }
405         if (count($actions) === 0) {
406             return '';
407         }
408         return html_writer::div(join(' ', $actions), 'listing-actions category-listing-actions mb-3');
409     }
411     /**
412      * Renders a course listing.
413      *
414      * @param core_course_category $category The currently selected category. This is what the listing is focused on.
415      * @param core_course_list_element $course The currently selected course.
416      * @param int $page The page being displayed.
417      * @param int $perpage The number of courses to display per page.
418      * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
419      * @return string
420      */
421     public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
422             $page = 0, $perpage = 20, $viewmode = 'default') {
424         if ($category === null) {
425             $html = html_writer::start_div('select-a-category');
426             $html .= html_writer::tag('h3', get_string('courses'),
427                 array('id' => 'course-listing-title', 'tabindex' => '0'));
428             $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
429             $html .= html_writer::end_div();
430             return $html;
431         }
433         $page = max($page, 0);
434         $perpage = max($perpage, 2);
435         $totalcourses = $category->coursecount;
436         $totalpages = ceil($totalcourses / $perpage);
437         if ($page > $totalpages - 1) {
438             $page = $totalpages - 1;
439         }
440         $options = array(
441             'offset' => $page * $perpage,
442             'limit' => $perpage
443         );
444         $courseid = isset($course) ? $course->id : null;
445         $class = '';
446         if ($page === 0) {
447             $class .= ' firstpage';
448         }
449         if ($page + 1 === (int)$totalpages) {
450             $class .= ' lastpage';
451         }
453         $html  = html_writer::start_div('card course-listing w-100'.$class, array(
454             'data-category' => $category->id,
455             'data-page' => $page,
456             'data-totalpages' => $totalpages,
457             'data-totalcourses' => $totalcourses,
458             'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
459         ));
460         $html .= html_writer::tag('h3', $category->get_formatted_name(),
461             array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
462         $html .= html_writer::start_div('card-body');
463         $html .= $this->course_listing_actions($category, $course, $perpage);
464         $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
465         $html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
466         foreach ($category->get_courses($options) as $listitem) {
467             $html .= $this->course_listitem($category, $listitem, $courseid);
468         }
469         $html .= html_writer::end_tag('ul');
470         $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
471         $html .= $this->course_bulk_actions($category);
472         $html .= html_writer::end_div();
473         $html .= html_writer::end_div();
474         return $html;
475     }
477     /**
478      * Renderers a course list item.
479      *
480      * This function will be called for every course being displayed by course_listing.
481      *
482      * @param core_course_category $category The currently selected category and the category the course belongs to.
483      * @param core_course_list_element $course The course to produce HTML for.
484      * @param int $selectedcourse The id of the currently selected course.
485      * @return string
486      */
487     public function course_listitem(core_course_category $category, core_course_list_element $course, $selectedcourse) {
489         $text = $course->get_formatted_name();
490         $attributes = array(
491             'class' => 'listitem listitem-course list-group-item list-group-item-action',
492             'data-id' => $course->id,
493             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
494             'data-visible' => $course->visible ? '1' : '0'
495         );
497         $bulkcourseinput = array(
498             'type' => 'checkbox',
499             'name' => 'bc[]',
500             'value' => $course->id,
501             'class' => 'bulk-action-checkbox',
502             'aria-label' => get_string('bulkactionselect', 'moodle', $text),
503             'data-action' => 'select'
504         );
505         if (!$category->has_manage_capability()) {
506             // Very very hardcoded here.
507             $bulkcourseinput['style'] = 'visibility:hidden';
508         }
510         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
512         $html  = html_writer::start_tag('li', $attributes);
513         $html .= html_writer::start_div('clearfix');
515         if ($category->can_resort_courses()) {
516             // In order for dnd to be available the user must be able to resort the category children..
517             $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
518         }
520         $html .= html_writer::start_div('ba-checkbox float-left');
521         $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
522         $html .= html_writer::end_div();
523         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
524         $html .= html_writer::start_div('float-right');
525         if ($course->idnumber) {
526             $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
527         }
528         $html .= $this->course_listitem_actions($category, $course);
529         $html .= html_writer::end_div();
530         $html .= html_writer::end_div();
531         $html .= html_writer::end_tag('li');
532         return $html;
533     }
535     /**
536      * Renderers actions for the course listing.
537      *
538      * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
539      *
540      * @param core_course_category $category
541      * @param core_course_list_element $course The currently selected course.
542      * @param int $perpage
543      * @return string
544      */
545     public function course_listing_actions(core_course_category $category, core_course_list_element $course = null, $perpage = 20) {
546         $actions = array();
547         if ($category->can_create_course()) {
548             $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
549             $actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-default'));
550         }
551         if ($category->can_request_course()) {
552             // Request a new course.
553             $url = new moodle_url('/course/request.php', array('return' => 'management'));
554             $actions[] = html_writer::link($url, get_string('requestcourse'));
555         }
556         if ($category->can_resort_courses()) {
557             $params = $this->page->url->params();
558             $params['action'] = 'resortcourses';
559             $params['sesskey'] = sesskey();
560             $baseurl = new moodle_url('/course/management.php', $params);
561             $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
562             $fullnameurldesc = new moodle_url($baseurl, array('resort' => 'fullnamedesc'));
563             $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
564             $shortnameurldesc = new moodle_url($baseurl, array('resort' => 'shortnamedesc'));
565             $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
566             $idnumberdescurl = new moodle_url($baseurl, array('resort' => 'idnumberdesc'));
567             $timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
568             $timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
569             $menu = new action_menu(array(
570                 new action_menu_link_secondary($fullnameurl,
571                                                null,
572                                                get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
573                 new action_menu_link_secondary($fullnameurldesc,
574                                                null,
575                                                get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
576                 new action_menu_link_secondary($shortnameurl,
577                                                null,
578                                                get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
579                 new action_menu_link_secondary($shortnameurldesc,
580                                                null,
581                                                get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
582                 new action_menu_link_secondary($idnumberurl,
583                                                null,
584                                                get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
585                 new action_menu_link_secondary($idnumberdescurl,
586                                                null,
587                                                get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
588                 new action_menu_link_secondary($timecreatedurl,
589                                                null,
590                                                get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
591                 new action_menu_link_secondary($timecreateddescurl,
592                                                null,
593                                                get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
594             ));
595             $menu->set_menu_trigger(get_string('resortcourses'));
596             $actions[] = $this->render($menu);
597         }
598         $strall = get_string('all');
599         $menu = new action_menu(array(
600             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
601             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
602             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
603             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
604             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
605             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
606         ));
607         if ((int)$perpage === 999) {
608             $perpage = $strall;
609         }
610         $menu->attributes['class'] .= ' courses-per-page';
611         $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
612         $actions[] = $this->render($menu);
613         return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
614     }
616     /**
617      * Displays a search result listing.
618      *
619      * @param array $courses The courses to display.
620      * @param int $totalcourses The total number of courses to display.
621      * @param core_course_list_element $course The currently selected course if there is one.
622      * @param int $page The current page, starting at 0.
623      * @param int $perpage The number of courses to display per page.
624      * @param string $search The string we are searching for.
625      * @return string
626      */
627     public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
628         $search = '') {
629         $page = max($page, 0);
630         $perpage = max($perpage, 2);
631         $totalpages = ceil($totalcourses / $perpage);
632         if ($page > $totalpages - 1) {
633             $page = $totalpages - 1;
634         }
635         $courseid = isset($course) ? $course->id : null;
636         $first = true;
637         $last = false;
638         $i = $page * $perpage;
640         $html  = html_writer::start_div('course-listing w-100', array(
641             'data-category' => 'search',
642             'data-page' => $page,
643             'data-totalpages' => $totalpages,
644             'data-totalcourses' => $totalcourses
645         ));
646         $html .= html_writer::tag('h3', get_string('courses'));
647         $html .= $this->search_pagination($totalcourses, $page, $perpage);
648         $html .= html_writer::start_tag('ul', array('class' => 'ml'));
649         foreach ($courses as $listitem) {
650             $i++;
651             if ($i == $totalcourses) {
652                 $last = true;
653             }
654             $html .= $this->search_listitem($listitem, $courseid, $first, $last);
655             $first = false;
656         }
657         $html .= html_writer::end_tag('ul');
658         $html .= $this->search_pagination($totalcourses, $page, $perpage, true, $search);
659         $html .= $this->course_search_bulk_actions();
660         $html .= html_writer::end_div();
661         return $html;
662     }
664     /**
665      * Renderers a search result course list item.
666      *
667      * This function will be called for every course being displayed by course_listing.
668      *
669      * @param core_course_list_element $course The course to produce HTML for.
670      * @param int $selectedcourse The id of the currently selected course.
671      * @return string
672      */
673     public function search_listitem(core_course_list_element $course, $selectedcourse) {
675         $text = $course->get_formatted_name();
676         $attributes = array(
677             'class' => 'listitem listitem-course list-group-item list-group-item-action',
678             'data-id' => $course->id,
679             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
680             'data-visible' => $course->visible ? '1' : '0'
681         );
682         $bulkcourseinput = '';
683         if (core_course_category::get($course->category)->can_move_courses_out_of()) {
684             $bulkcourseinput = array(
685                 'type' => 'checkbox',
686                 'name' => 'bc[]',
687                 'value' => $course->id,
688                 'class' => 'bulk-action-checkbox',
689                 'aria-label' => get_string('bulkactionselect', 'moodle', $text),
690                 'data-action' => 'select'
691             );
692         }
693         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
694         $categoryname = core_course_category::get($course->category)->get_formatted_name();
696         $html  = html_writer::start_tag('li', $attributes);
697         $html .= html_writer::start_div('clearfix');
698         $html .= html_writer::start_div('float-left');
699         if ($bulkcourseinput) {
700             $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
701         }
702         $html .= html_writer::end_div();
703         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
704         $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left categoryname'));
705         $html .= html_writer::start_div('float-right');
706         $html .= $this->search_listitem_actions($course);
707         $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
708         $html .= html_writer::end_div();
709         $html .= html_writer::end_div();
710         $html .= html_writer::end_tag('li');
711         return $html;
712     }
714     /**
715      * Renderers a key value pair of information for display.
716      *
717      * @param string $key
718      * @param string $value
719      * @param string $class
720      * @return string
721      */
722     protected function detail_pair($key, $value, $class ='') {
723         $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
724         $html .= html_writer::div(html_writer::span($key), 'pair-key col-md-3 yui3-u-1-4 font-weight-bold');
725         $html .= html_writer::div(html_writer::span($value), 'pair-value col-md-8 yui3-u-3-4');
726         $html .= html_writer::end_div();
727         return $html;
728     }
730     /**
731      * A collection of actions for a course.
732      *
733      * @param core_course_list_element $course The course to display actions for.
734      * @return string
735      */
736     public function course_detail_actions(core_course_list_element $course) {
737         $actions = \core_course\management\helper::get_course_detail_actions($course);
738         if (empty($actions)) {
739             return '';
740         }
741         $options = array();
742         foreach ($actions as $action) {
743             $options[] = $this->action_link($action['url'], $action['string'], null,
744                 array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
745         }
746         return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
747     }