MDL-69454 core_search: consistent course management search
[moodle.git] / course / classes / management_renderer.php
CommitLineData
5dc361e1
SH
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Contains renderers for the course management pages.
19 *
20 * @package core_course
21 * @copyright 2013 Sam Hemelryk
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die;
26
27require_once($CFG->dirroot.'/course/renderer.php');
28
29/**
30 * Main renderer for the course management pages.
31 *
32 * @package core_course
33 * @copyright 2013 Sam Hemelryk
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
36class core_course_management_renderer extends plugin_renderer_base {
37
38 /**
39 * Initialises the JS required to enhance the management interface.
40 *
41 * Thunderbirds are go, this function kicks into gear the JS that makes the
42 * course management pages that much cooler.
43 */
44 public function enhance_management_interface() {
484c4c6c 45 $this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
5dc361e1 46 $this->page->requires->strings_for_js(
d0647301
SH
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 ),
5dc361e1
SH
60 'moodle'
61 );
62 }
63
64 /**
65 * Displays a heading for the management pages.
66 *
67 * @param string $heading The heading to display
68 * @param string|null $viewmode The current view mode if there are options.
69 * @param int|null $categoryid The currently selected category if there is one.
70 * @return string
71 */
72 public function management_heading($heading, $viewmode = null, $categoryid = null) {
73 $html = html_writer::start_div('coursecat-management-header clearfix');
74 if (!empty($heading)) {
75 $html .= $this->heading($heading);
76 }
77 if ($viewmode !== null) {
d0647301
SH
78 $html .= html_writer::start_div();
79 $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
5dc361e1 80 if ($viewmode === 'courses') {
442f12f8 81 $categories = core_course_category::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
5dc361e1
SH
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);
c3015dd0 88 $select->attributes['aria-label'] = get_string('selectacategory');
5dc361e1
SH
89 $html .= $this->render($select);
90 }
d0647301 91 $html .= html_writer::end_div();
5dc361e1
SH
92 }
93 $html .= html_writer::end_div();
94 return $html;
95 }
96
97 /**
98 * Prepares the form element for the course category listing bulk actions.
99 *
100 * @return string
101 */
102 public function management_form_start() {
103 $form = array('action' => $this->page->url->out(), 'method' => 'POST', 'id' => 'coursecat-management');
104
105 $html = html_writer::start_tag('form', $form);
106 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
107 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'bulkaction'));
108 return $html;
109 }
110
111 /**
112 * Closes the course category bulk management form.
113 *
114 * @return string
115 */
116 public function management_form_end() {
117 return html_writer::end_tag('form');
118 }
119
120 /**
121 * Presents a course category listing.
122 *
442f12f8 123 * @param core_course_category $category The currently selected category. Also the category to highlight in the listing.
5dc361e1
SH
124 * @return string
125 */
442f12f8 126 public function category_listing(core_course_category $category = null) {
5dc361e1
SH
127
128 if ($category === null) {
5dc361e1 129 $selectedparents = array();
f454e324 130 $selectedcategory = null;
5dc361e1
SH
131 } else {
132 $selectedparents = $category->get_parents();
133 $selectedparents[] = $category->id;
f454e324 134 $selectedcategory = $category->id;
5dc361e1 135 }
d5de8747
SH
136 $catatlevel = \core_course\management\helper::get_expanded_categories('');
137 $catatlevel[] = array_shift($selectedparents);
138 $catatlevel = array_unique($catatlevel);
5dc361e1 139
beff3806 140 $listing = core_course_category::top()->get_children();
5dc361e1 141
7fd307b1 142 $attributes = array(
9f0595c4
MM
143 'class' => 'ml-1 list-unstyled',
144 'role' => 'tree',
145 'aria-labelledby' => 'category-listing-title'
7fd307b1
SH
146 );
147
9f0595c4
MM
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');
b488058f 152 $html .= $this->category_listing_actions($category);
7fd307b1 153 $html .= html_writer::start_tag('ul', $attributes);
5dc361e1
SH
154 foreach ($listing as $listitem) {
155 // Render each category in the listing.
156 $subcategories = array();
d5de8747 157 if (in_array($listitem->id, $catatlevel)) {
5dc361e1
SH
158 $subcategories = $listitem->get_children();
159 }
160 $html .= $this->category_listitem(
9f0595c4
MM
161 $listitem,
162 $subcategories,
163 $listitem->get_children_count(),
164 $selectedcategory,
165 $selectedparents
5dc361e1
SH
166 );
167 }
168 $html .= html_writer::end_tag('ul');
c7a2291f 169 $html .= $this->category_bulk_actions($category);
5dc361e1 170 $html .= html_writer::end_div();
9f0595c4 171 $html .= html_writer::end_div();
5dc361e1
SH
172 return $html;
173 }
174
175 /**
176 * Renders a category list item.
177 *
178 * This function gets called recursively to render sub categories.
179 *
442f12f8
MG
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.
5dc361e1
SH
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 */
442f12f8 187 public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
9f0595c4 188 $selectedcategory = null, $selectedcategories = array()) {
d5de8747 189
5dc361e1
SH
190 $isexpandable = ($totalsubcategories > 0);
191 $isexpanded = (!empty($subcategories));
f454e324 192 $activecategory = ($selectedcategory === $category->id);
5dc361e1 193 $attributes = array(
9f0595c4
MM
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'
5dc361e1
SH
202 );
203 $text = $category->get_formatted_name();
beff3806 204 if (($parent = $category->get_parent_coursecat()) && $parent->id) {
d0647301
SH
205 $a = new stdClass;
206 $a->category = $text;
beff3806 207 $a->parentcategory = $parent->get_formatted_name();
d0647301
SH
208 $textlabel = get_string('categorysubcategoryof', 'moodle', $a);
209 }
5dc361e1 210 $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
cd96b7d3 211 $bcatinput = array(
a521226d 212 'id' => 'categorylistitem' . $category->id,
9f0595c4
MM
213 'type' => 'checkbox',
214 'name' => 'bcat[]',
215 'value' => $category->id,
a521226d 216 'class' => 'bulk-action-checkbox custom-control-input',
9f0595c4 217 'data-action' => 'select'
cd96b7d3 218 );
b488058f 219
a521226d 220 $checkboxclass = '';
b488058f
SH
221 if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
222 // Very very hardcoded here.
a521226d 223 $checkboxclass = 'd-none';
b488058f
SH
224 }
225
5dc361e1
SH
226 $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
227 if ($isexpanded) {
9f0595c4
MM
228 $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'),
229 'moodle', array('class' => 'tree-icon', 'title' => ''));
d0647301 230 $icon = html_writer::link(
9f0595c4
MM
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 )
d0647301 239 );
5dc361e1 240 } else if ($isexpandable) {
9f0595c4
MM
241 $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'),
242 'moodle', array('class' => 'tree-icon', 'title' => ''));
d0647301 243 $icon = html_writer::link(
9f0595c4
MM
244 $viewcaturl,
245 $icon,
246 array(
247 'class' => 'float-left',
248 'data-action' => 'expand',
249 'title' => get_string('expandcategory', 'moodle', $text)
250 )
d0647301 251 );
5dc361e1 252 } else {
d0647301 253 $icon = $this->output->pix_icon(
9f0595c4
MM
254 'i/empty',
255 '',
256 'moodle',
257 array('class' => 'tree-icon'));
d0647301 258 $icon = html_writer::span($icon, 'float-left');
5dc361e1 259 }
b488058f 260 $actions = \core_course\management\helper::get_category_listitem_actions($category);
484c4c6c 261 $hasactions = !empty($actions) || $category->can_create_course();
5dc361e1
SH
262
263 $html = html_writer::start_tag('li', $attributes);
264 $html .= html_writer::start_div('clearfix');
a521226d
BB
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);
c3015dd0
JP
268 $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
269 $html .= html_writer::tag('label', $labeltext, array(
a521226d
BB
270 'class' => 'custom-control-label',
271 'for' => 'categorylistitem' . $category->id));
272 $html .= html_writer::end_div();
5dc361e1
SH
273 $html .= html_writer::end_div();
274 $html .= $icon;
b488058f 275 if ($hasactions) {
91de0ed8 276 $textattributes = array('class' => 'float-left categoryname aalink');
b488058f 277 } else {
d0647301
SH
278 $textattributes = array('class' => 'float-left categoryname without-actions');
279 }
280 if (isset($textlabel)) {
281 $textattributes['aria-label'] = $textlabel;
b488058f 282 }
d0647301 283 $html .= html_writer::link($viewcaturl, $text, $textattributes);
9f0595c4 284 $html .= html_writer::start_div('float-right d-flex');
c7a2291f 285 if ($category->idnumber) {
a521226d 286 $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'text-muted idnumber'));
c7a2291f 287 }
b488058f
SH
288 if ($hasactions) {
289 $html .= $this->category_listitem_actions($category, $actions);
290 }
7fd307b1 291 $countid = 'course-count-'.$category->id;
38a15200 292 $html .= html_writer::span(
9f0595c4
MM
293 html_writer::span($category->get_courses_count()) .
294 html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
295 $courseicon,
a521226d 296 'course-count text-muted',
9f0595c4 297 array('aria-labelledby' => $countid)
38a15200 298 );
5dc361e1
SH
299 $html .= html_writer::end_div();
300 $html .= html_writer::end_div();
301 if ($isexpanded) {
d0647301 302 $html .= html_writer::start_tag('ul',
9f0595c4 303 array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
d5de8747
SH
304 $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
305 $catatlevel[] = array_shift($selectedcategories);
306 $catatlevel = array_unique($catatlevel);
5dc361e1 307 foreach ($subcategories as $listitem) {
d5de8747 308 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
5dc361e1 309 $html .= $this->category_listitem(
9f0595c4
MM
310 $listitem,
311 $childcategories,
312 $listitem->get_children_count(),
313 $selectedcategory,
314 $selectedcategories
5dc361e1
SH
315 );
316 }
317 $html .= html_writer::end_tag('ul');
318 }
319 $html .= html_writer::end_tag('li');
320 return $html;
321 }
322
323 /**
324 * Renderers the actions that are possible for the course category listing.
325 *
326 * These are not the actions associated with an individual category listing.
327 * That happens through category_listitem_actions.
328 *
442f12f8 329 * @param core_course_category $category
5dc361e1
SH
330 * @return string
331 */
442f12f8 332 public function category_listing_actions(core_course_category $category = null) {
5dc361e1 333 $actions = array();
017518d4
SH
334
335 $cancreatecategory = $category && $category->can_create_subcategory();
442f12f8 336 $cancreatecategory = $cancreatecategory || core_course_category::can_create_top_level_category();
b488058f 337 if ($category === null) {
beff3806 338 $category = core_course_category::top();
b488058f 339 }
5dc361e1 340
017518d4
SH
341 if ($cancreatecategory) {
342 $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
29551c4b 343 $actions[] = html_writer::link($url, get_string('createnewcategory'), array('class' => 'btn btn-secondary'));
5dc361e1 344 }
442f12f8 345 if (core_course_category::can_approve_course_requests()) {
484c4c6c
SH
346 $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
347 }
c7a2291f 348 if (count($actions) === 0) {
5dc361e1
SH
349 return '';
350 }
9f0595c4 351 return html_writer::div(join(' ', $actions), 'listing-actions category-listing-actions mb-3');
5dc361e1
SH
352 }
353
354 /**
355 * Renderers the actions for individual category list items.
356 *
442f12f8 357 * @param core_course_category $category
c7a2291f 358 * @param array $actions
5dc361e1
SH
359 * @return string
360 */
442f12f8 361 public function category_listitem_actions(core_course_category $category, array $actions = null) {
b488058f
SH
362 if ($actions === null) {
363 $actions = \core_course\management\helper::get_category_listitem_actions($category);
364 }
5dc361e1
SH
365 $menu = new action_menu();
366 $menu->attributes['class'] .= ' category-item-actions item-actions';
367 $hasitems = false;
b488058f 368 foreach ($actions as $key => $action) {
5dc361e1
SH
369 $hasitems = true;
370 $menu->add(new action_menu_link(
371 $action['url'],
372 $action['icon'],
373 $action['string'],
374 in_array($key, array('show', 'hide', 'moveup', 'movedown')),
375 array('data-action' => $key, 'class' => 'action-'.$key)
376 ));
377 }
378 if (!$hasitems) {
379 return '';
380 }
381 return $this->render($menu);
382 }
383
63e4df60 384 public function render_action_menu($menu) {
1dcd0d34 385 return $this->output->render($menu);
63e4df60
DW
386 }
387
5dc361e1
SH
388 /**
389 * Renders bulk actions for categories.
390 *
442f12f8 391 * @param core_course_category $category The currently selected category if there is one.
5dc361e1
SH
392 * @return string
393 */
442f12f8 394 public function category_bulk_actions(core_course_category $category = null) {
5dc361e1
SH
395 // Resort courses.
396 // Change parent.
442f12f8 397 if (!core_course_category::can_resort_any() && !core_course_category::can_change_parent_any()) {
cd96b7d3
SH
398 return '';
399 }
5dc361e1
SH
400 $strgo = new lang_string('go');
401
402 $html = html_writer::start_div('category-bulk-actions bulk-actions');
cd96b7d3 403 $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
442f12f8 404 if (core_course_category::can_resort_any()) {
c7a2291f
SH
405 $selectoptions = array(
406 'selectedcategories' => get_string('selectedcategories'),
407 'allcategories' => get_string('allcategories')
5dc361e1 408 );
c7a2291f
SH
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(
c7a2291f
SH
415 html_writer::select(
416 $selectoptions,
417 'selectsortby',
e69a0251 418 'selectedcategories',
d0647301
SH
419 false,
420 array('aria-label' => get_string('selectcategorysort'))
c7a2291f 421 )
7fd307b1 422 );
c7a2291f 423 $form .= html_writer::div(
c7a2291f
SH
424 html_writer::select(
425 array(
9a4231e9
S
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')),
c7a2291f
SH
430 'none' => get_string('dontsortcategories')
431 ),
432 'resortcategoriesby',
e69a0251 433 'name',
d0647301 434 false,
5cac5fa4 435 array('aria-label' => get_string('selectcategorysortby'), 'class' => 'mt-1')
c7a2291f
SH
436 )
437 );
438 $form .= html_writer::div(
c7a2291f
SH
439 html_writer::select(
440 array(
9a4231e9
S
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')),
c7a2291f
SH
449 'none' => get_string('dontsortcourses')
450 ),
451 'resortcoursesby',
e69a0251 452 'fullname',
d0647301 453 false,
5cac5fa4 454 array('aria-label' => get_string('selectcoursesortby'), 'class' => 'mt-1')
c7a2291f 455 )
7fd307b1 456 );
6dccde9d 457 $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort',
5cac5fa4 458 'value' => get_string('sort'), 'class' => 'btn btn-secondary my-1'));
c7a2291f 459 $form .= html_writer::end_div();
d0647301 460
5cac5fa4 461 $html .= html_writer::start_div('detail-pair row yui3-g my-1');
6ecc4459
MM
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');
d0647301 464 $html .= html_writer::end_div();
5dc361e1 465 }
442f12f8 466 if (core_course_category::can_change_parent_any()) {
3b732cd6 467 $options = array();
beff3806
MG
468 if (core_course_category::top()->has_manage_capability()) {
469 $options[0] = core_course_category::top()->get_formatted_name();
3b732cd6 470 }
442f12f8 471 $options += core_course_category::make_categories_list('moodle/category:manage');
7fd307b1
SH
472 $select = html_writer::select(
473 $options,
474 'movecategoriesto',
475 '',
476 array('' => 'choosedots'),
5cac5fa4 477 array('aria-labelledby' => 'moveselectedcategoriesto', 'class' => 'mr-1')
7fd307b1 478 );
6dccde9d
SL
479 $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'),
480 'class' => 'btn btn-secondary');
5dc361e1 481 $html .= $this->detail_pair(
7fd307b1 482 html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
5dc361e1
SH
483 $select . html_writer::empty_tag('input', $submit)
484 );
485 }
486 $html .= html_writer::end_div();
487 return $html;
488 }
489
490 /**
491 * Renders a course listing.
492 *
442f12f8 493 * @param core_course_category $category The currently selected category. This is what the listing is focused on.
9f0595c4 494 * @param core_course_list_element $course The currently selected course.
5dc361e1
SH
495 * @param int $page The page being displayed.
496 * @param int $perpage The number of courses to display per page.
d29bde59 497 * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
5dc361e1
SH
498 * @return string
499 */
442f12f8 500 public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
9f0595c4 501 $page = 0, $perpage = 20, $viewmode = 'default') {
5dc361e1
SH
502
503 if ($category === null) {
504 $html = html_writer::start_div('select-a-category');
d0647301 505 $html .= html_writer::tag('h3', get_string('courses'),
9f0595c4 506 array('id' => 'course-listing-title', 'tabindex' => '0'));
5dc361e1
SH
507 $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
508 $html .= html_writer::end_div();
509 return $html;
510 }
511
512 $page = max($page, 0);
513 $perpage = max($perpage, 2);
514 $totalcourses = $category->coursecount;
515 $totalpages = ceil($totalcourses / $perpage);
516 if ($page > $totalpages - 1) {
517 $page = $totalpages - 1;
518 }
519 $options = array(
9f0595c4
MM
520 'offset' => $page * $perpage,
521 'limit' => $perpage
5dc361e1
SH
522 );
523 $courseid = isset($course) ? $course->id : null;
484c4c6c
SH
524 $class = '';
525 if ($page === 0) {
526 $class .= ' firstpage';
527 }
528 if ($page + 1 === (int)$totalpages) {
529 $class .= ' lastpage';
530 }
5dc361e1 531
9f0595c4
MM
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()
5dc361e1 538 ));
d0647301 539 $html .= html_writer::tag('h3', $category->get_formatted_name(),
9f0595c4
MM
540 array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
541 $html .= html_writer::start_div('card-body');
5dc361e1 542 $html .= $this->course_listing_actions($category, $course, $perpage);
d29bde59 543 $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
c3015dd0 544 $html .= html_writer::start_tag('ul', array('class' => 'ml course-list'));
5dc361e1 545 foreach ($category->get_courses($options) as $listitem) {
f454e324 546 $html .= $this->course_listitem($category, $listitem, $courseid);
5dc361e1
SH
547 }
548 $html .= html_writer::end_tag('ul');
d29bde59 549 $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
5dc361e1
SH
550 $html .= $this->course_bulk_actions($category);
551 $html .= html_writer::end_div();
9f0595c4 552 $html .= html_writer::end_div();
5dc361e1
SH
553 return $html;
554 }
555
556 /**
557 * Renders pagination for a course listing.
558 *
442f12f8 559 * @param core_course_category $category The category to produce pagination for.
5dc361e1
SH
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.
d29bde59 563 * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
5dc361e1
SH
564 * @return string
565 */
442f12f8
MG
566 protected function listing_pagination(core_course_category $category, $page, $perpage, $showtotals = false,
567 $viewmode = 'default') {
5dc361e1 568 $html = '';
d61d3163 569 $totalcourses = $category->get_courses_count();
5dc361e1
SH
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 }
a521226d 583 $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
5dc361e1
SH
584 }
585
d29bde59
LB
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 }
5dc361e1 592
c8d5001f 593 $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
5dc361e1
SH
594 return $html;
595 }
596
597 /**
598 * Renderers a course list item.
599 *
600 * This function will be called for every course being displayed by course_listing.
601 *
442f12f8 602 * @param core_course_category $category The currently selected category and the category the course belongs to.
9f0595c4 603 * @param core_course_list_element $course The course to produce HTML for.
5dc361e1 604 * @param int $selectedcourse The id of the currently selected course.
5dc361e1
SH
605 * @return string
606 */
442f12f8 607 public function course_listitem(core_course_category $category, core_course_list_element $course, $selectedcourse) {
5dc361e1
SH
608
609 $text = $course->get_formatted_name();
610 $attributes = array(
9f0595c4
MM
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'
5dc361e1
SH
615 );
616
d0647301 617 $bulkcourseinput = array(
a521226d 618 'id' => 'courselistitem' . $course->id,
9f0595c4
MM
619 'type' => 'checkbox',
620 'name' => 'bc[]',
621 'value' => $course->id,
a521226d 622 'class' => 'bulk-action-checkbox custom-control-input',
9f0595c4 623 'data-action' => 'select'
d0647301 624 );
a521226d
BB
625
626 $checkboxclass = '';
484c4c6c
SH
627 if (!$category->has_manage_capability()) {
628 // Very very hardcoded here.
a521226d 629 $checkboxclass = 'd-none';
484c4c6c
SH
630 }
631
5dc361e1
SH
632 $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
633
634 $html = html_writer::start_tag('li', $attributes);
635 $html .= html_writer::start_div('clearfix');
636
67e1f268 637 if ($category->can_resort_courses()) {
5dc361e1 638 // In order for dnd to be available the user must be able to resort the category children..
10a2bf46 639 $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
5dc361e1
SH
640 }
641
a521226d
BB
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);
c3015dd0
JP
645 $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
646 $html .= html_writer::tag('label', $labeltext, array(
a521226d
BB
647 'class' => 'custom-control-label',
648 'for' => 'courselistitem' . $course->id));
649 $html .= html_writer::end_div();
5dc361e1 650 $html .= html_writer::end_div();
91de0ed8 651 $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
5dc361e1 652 $html .= html_writer::start_div('float-right');
c7a2291f 653 if ($course->idnumber) {
a521226d 654 $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
c7a2291f 655 }
f454e324 656 $html .= $this->course_listitem_actions($category, $course);
5dc361e1
SH
657 $html .= html_writer::end_div();
658 $html .= html_writer::end_div();
659 $html .= html_writer::end_tag('li');
660 return $html;
661 }
662
663 /**
664 * Renderers actions for the course listing.
665 *
666 * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
667 *
442f12f8
MG
668 * @param core_course_category $category
669 * @param core_course_list_element $course The currently selected course.
5dc361e1
SH
670 * @param int $perpage
671 * @return string
672 */
442f12f8 673 public function course_listing_actions(core_course_category $category, core_course_list_element $course = null, $perpage = 20) {
5dc361e1
SH
674 $actions = array();
675 if ($category->can_create_course()) {
676 $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
29551c4b 677 $actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-secondary'));
5dc361e1 678 }
484c4c6c
SH
679 if ($category->can_request_course()) {
680 // Request a new course.
3e15abe5 681 $url = new moodle_url('/course/request.php', array('category' => $category->id, 'return' => 'management'));
484c4c6c
SH
682 $actions[] = html_writer::link($url, get_string('requestcourse'));
683 }
67e1f268 684 if ($category->can_resort_courses()) {
5dc361e1
SH
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'));
9a4231e9 690 $fullnameurldesc = new moodle_url($baseurl, array('resort' => 'fullnamedesc'));
5dc361e1 691 $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
9a4231e9 692 $shortnameurldesc = new moodle_url($baseurl, array('resort' => 'shortnamedesc'));
5dc361e1 693 $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
9a4231e9
S
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'));
5dc361e1 697 $menu = new action_menu(array(
9f0595c4
MM
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')))
5dc361e1 722 ));
f5b33487 723 $menu->set_menu_trigger(get_string('resortcourses'));
5dc361e1
SH
724 $actions[] = $this->render($menu);
725 }
726 $strall = get_string('all');
727 $menu = new action_menu(array(
9f0595c4
MM
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),
5dc361e1 734 ));
7fd307b1 735 if ((int)$perpage === 999) {
5dc361e1
SH
736 $perpage = $strall;
737 }
738 $menu->attributes['class'] .= ' courses-per-page';
f5b33487 739 $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
5dc361e1 740 $actions[] = $this->render($menu);
9f0595c4 741 return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
5dc361e1
SH
742 }
743
744 /**
745 * Renderers actions for individual course actions.
746 *
442f12f8
MG
747 * @param core_course_category $category The currently selected category.
748 * @param core_course_list_element $course The course to renderer actions for.
5dc361e1
SH
749 * @return string
750 */
442f12f8 751 public function course_listitem_actions(core_course_category $category, core_course_list_element $course) {
484c4c6c 752 $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
5dc361e1
SH
753 if (empty($actions)) {
754 return '';
755 }
484c4c6c
SH
756 $actionshtml = array();
757 foreach ($actions as $action) {
7fd307b1 758 $action['attributes']['role'] = 'button';
484c4c6c
SH
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');
5dc361e1
SH
762 }
763
764 /**
765 * Renderers bulk actions that can be performed on courses.
766 *
442f12f8 767 * @param core_course_category $category The currently selected category and the category in which courses that
5dc361e1
SH
768 * are selectable belong.
769 * @return string
770 */
442f12f8 771 public function course_bulk_actions(core_course_category $category) {
5dc361e1
SH
772 $html = html_writer::start_div('course-bulk-actions bulk-actions');
773 if ($category->can_move_courses_out_of()) {
cd96b7d3 774 $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
442f12f8 775 $options = core_course_category::make_categories_list('moodle/category:manage');
7fd307b1
SH
776 $select = html_writer::select(
777 $options,
778 'movecoursesto',
779 '',
780 array('' => 'choosedots'),
5cac5fa4 781 array('aria-labelledby' => 'moveselectedcoursesto', 'class' => 'mr-1')
7fd307b1 782 );
6dccde9d
SL
783 $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
784 'class' => 'btn btn-secondary');
5dc361e1 785 $html .= $this->detail_pair(
7fd307b1 786 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
5dc361e1
SH
787 $select . html_writer::empty_tag('input', $submit)
788 );
789 }
790 $html .= html_writer::end_div();
791 return $html;
792 }
793
fe3999ec
WL
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'));
442f12f8 802 $options = core_course_category::make_categories_list('moodle/category:manage');
fe3999ec
WL
803 $select = html_writer::select(
804 $options,
805 'movecoursesto',
806 '',
807 array('' => 'choosedots'),
808 array('aria-labelledby' => 'moveselectedcoursesto')
809 );
6dccde9d
SL
810 $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
811 'class' => 'btn btn-secondary');
fe3999ec
WL
812 $html .= $this->detail_pair(
813 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
814 $select . html_writer::empty_tag('input', $submit)
815 );
816 $html .= html_writer::end_div();
817 return $html;
818 }
819
5dc361e1
SH
820 /**
821 * Renderers detailed course information.
822 *
9f0595c4 823 * @param core_course_list_element $course The course to display details for.
5dc361e1
SH
824 * @return string
825 */
442f12f8 826 public function course_detail(core_course_list_element $course) {
5dc361e1
SH
827 $details = \core_course\management\helper::get_course_detail_array($course);
828 $fullname = $details['fullname']['value'];
829
9f0595c4
MM
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');
5dc361e1
SH
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();
9f0595c4 841 $html .= html_writer::end_div();
5dc361e1
SH
842 return $html;
843 }
844
845 /**
846 * Renderers a key value pair of information for display.
847 *
848 * @param string $key
849 * @param string $value
850 * @param string $class
851 * @return string
852 */
853 protected function detail_pair($key, $value, $class ='') {
484c4c6c 854 $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
9f0595c4
MM
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');
5dc361e1
SH
857 $html .= html_writer::end_div();
858 return $html;
859 }
860
861 /**
862 * A collection of actions for a course.
863 *
9f0595c4 864 * @param core_course_list_element $course The course to display actions for.
5dc361e1
SH
865 * @return string
866 */
442f12f8 867 public function course_detail_actions(core_course_list_element $course) {
5dc361e1
SH
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) {
9f0595c4
MM
874 $options[] = $this->action_link($action['url'], $action['string'], null,
875 array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
5dc361e1 876 }
9f0595c4 877 return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
5dc361e1
SH
878 }
879
880 /**
881 * Creates an action button (styled link)
882 *
883 * @param moodle_url $url The URL to go to when clicked.
884 * @param string $text The text for the button.
885 * @param string $id An id to give the button.
886 * @param string $class A class to give the button.
d0647301 887 * @param array $attributes Any additional attributes
5dc361e1
SH
888 * @return string
889 */
d0647301
SH
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 }
5dc361e1
SH
896 if (!is_null($id)) {
897 $attributes['id'] = $id;
898 }
899 if (!is_null($class)) {
900 $attributes['class'] .= ' '.$class;
901 }
7fd307b1
SH
902 if (is_null($title)) {
903 $title = $text;
904 }
905 $attributes['title'] = $title;
d0647301
SH
906 if (!isset($attributes['role'])) {
907 $attributes['role'] = 'button';
908 }
5dc361e1
SH
909 return html_writer::link($url, $text, $attributes);
910 }
911
912 /**
913 * Opens a grid.
914 *
915 * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
916 *
917 * @param string $id An id to give this grid.
918 * @param string $class A class to give this grid.
919 * @return string
920 */
921 public function grid_start($id = null, $class = null) {
9f0595c4 922 $gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
5dc361e1
SH
923 if (is_null($class)) {
924 $class = $gridclass;
925 } else {
926 $class .= ' ' . $gridclass;
927 }
928 $attributes = array();
929 if (!is_null($id)) {
930 $attributes['id'] = $id;
931 }
932 return html_writer::start_div($class, $attributes);
933 }
934
935 /**
936 * Closes the grid.
937 *
938 * @return string
939 */
940 public function grid_end() {
941 return html_writer::end_div();
942 }
943
944 /**
945 * Opens a grid column
946 *
947 * @param int $size The number of segments this column should span.
948 * @param string $id An id to give the column.
949 * @param string $class A class to give the column.
950 * @return string
951 */
952 public function grid_column_start($size, $id = null, $class = null) {
953
9f0595c4
MM
954 if ($id == 'course-detail') {
955 $size = 12;
956 $bootstrapclass = 'col-md-'.$size;
5dc361e1 957 } else {
9f0595c4 958 $bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
5dc361e1
SH
959 }
960
9f0595c4
MM
961 $yuigridclass = "col-sm";
962
5dc361e1
SH
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 }
9f0595c4 972 return html_writer::start_div($class . " grid_column_start", $attributes);
5dc361e1
SH
973 }
974
975 /**
976 * Closes a grid column.
977 *
978 * @return string
979 */
980 public function grid_column_end() {
981 return html_writer::end_div();
982 }
983
984 /**
985 * Renders an action_icon.
986 *
987 * This function uses the {@link core_renderer::action_link()} method for the
988 * most part. What it does different is prepare the icon as HTML and use it
989 * as the link text.
990 *
991 * @param string|moodle_url $url A string URL or moodel_url
992 * @param pix_icon $pixicon
993 * @param component_action $action
994 * @param array $attributes associative array of html link attributes + disabled
995 * @param bool $linktext show title next to image in link
996 * @return string HTML fragment
997 */
998 public function action_icon($url, pix_icon $pixicon, component_action $action = null,
999 array $attributes = null, $linktext = false) {
1000 if (!($url instanceof moodle_url)) {
1001 $url = new moodle_url($url);
1002 }
1003 $attributes = (array)$attributes;
1004
1005 if (empty($attributes['class'])) {
1006 // Let devs override the class via $attributes.
1007 $attributes['class'] = 'action-icon';
1008 }
1009
1010 $icon = $this->render($pixicon);
1011
1012 if ($linktext) {
1013 $text = $pixicon->attributes['alt'];
1014 } else {
1015 $text = '';
1016 }
1017
1018 return $this->action_link($url, $icon.$text, $action, $attributes);
1019 }
1020
1021 /**
1022 * Displays a view mode selector.
1023 *
1024 * @param array $modes An array of view modes.
1025 * @param string $currentmode The current view mode.
1026 * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
1027 * @param string $param The param name.
1028 * @return string
1029 */
1030 public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
1031 if ($url === null) {
1032 $url = $this->page->url;
1033 }
1034
1035 $menu = new action_menu;
c8d5001f 1036 $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
5dc361e1
SH
1037
1038 $selected = null;
1039 foreach ($modes as $mode => $modestr) {
1040 $attributes = array(
1041 'class' => 'vms-mode',
1042 'data-mode' => $mode
1043 );
1044 if ($currentmode === $mode) {
1045 $attributes['class'] .= ' currentmode';
1046 $selected = $modestr;
1047 }
1048 if ($selected === null) {
1049 $selected = $modestr;
1050 }
1051 $modeurl = new moodle_url($url, array($param => $mode));
1052 if ($mode === 'default') {
1053 $modeurl->remove_params($param);
1054 }
1055 $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
1056 }
1057
aa861f62 1058 $menu->set_menu_trigger($selected);
5dc361e1 1059
c8d5001f 1060 $html = html_writer::start_div('view-mode-selector vms d-flex');
aa861f62 1061 $html .= get_string('viewing').' '.$this->render($menu);
5dc361e1
SH
1062 $html .= html_writer::end_div();
1063
1064 return $html;
1065 }
1066
1067 /**
1068 * Displays a search result listing.
1069 *
1070 * @param array $courses The courses to display.
1071 * @param int $totalcourses The total number of courses to display.
9f0595c4 1072 * @param core_course_list_element $course The currently selected course if there is one.
5dc361e1
SH
1073 * @param int $page The current page, starting at 0.
1074 * @param int $perpage The number of courses to display per page.
09fef88c 1075 * @param string $search The string we are searching for.
5dc361e1
SH
1076 * @return string
1077 */
442f12f8 1078 public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
9f0595c4 1079 $search = '') {
5dc361e1
SH
1080 $page = max($page, 0);
1081 $perpage = max($perpage, 2);
1082 $totalpages = ceil($totalcourses / $perpage);
1083 if ($page > $totalpages - 1) {
1084 $page = $totalpages - 1;
1085 }
1086 $courseid = isset($course) ? $course->id : null;
1087 $first = true;
1088 $last = false;
1089 $i = $page * $perpage;
1090
9f0595c4
MM
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
5dc361e1
SH
1096 ));
1097 $html .= html_writer::tag('h3', get_string('courses'));
1098 $html .= $this->search_pagination($totalcourses, $page, $perpage);
89e78fd0 1099 $html .= html_writer::start_tag('ul', array('class' => 'ml'));
5dc361e1
SH
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');
09fef88c 1109 $html .= $this->search_pagination($totalcourses, $page, $perpage, true, $search);
fe3999ec 1110 $html .= $this->course_search_bulk_actions();
5dc361e1
SH
1111 $html .= html_writer::end_div();
1112 return $html;
1113 }
1114
1115 /**
1116 * Displays pagination for search results.
1117 *
1118 * @param int $totalcourses The total number of courses to be displayed.
1119 * @param int $page The current page.
1120 * @param int $perpage The number of courses being displayed.
1121 * @param bool $showtotals Whether or not to print total information.
09fef88c 1122 * @param string $search The string we are searching for.
5dc361e1
SH
1123 * @return string
1124 */
09fef88c 1125 protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false, $search = '') {
5dc361e1
SH
1126 $html = '';
1127 $totalpages = ceil($totalcourses / $perpage);
1128 if ($showtotals) {
09fef88c 1129 if ($totalpages == 0) {
835ca041 1130 $str = get_string('nocoursesfound', 'moodle', s($search));
09fef88c 1131 } else if ($totalpages == 1) {
5dc361e1
SH
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 }
a521226d 1140 $html .= html_writer::div($str, 'listing-pagination-totals text-muted');
5dc361e1
SH
1141 }
1142
1143 if ($totalcourses < $perpage) {
1144 return $html;
1145 }
1146 $aside = 2;
1147 $span = $aside * 2 + 1;
1148 $start = max($page - $aside, 0);
1149 $end = min($page + $aside, $totalpages - 1);
1150 if (($end - $start) < $span) {
1151 if ($start == 0) {
1152 $end = min($totalpages - 1, $span - 1);
1153 } else if ($end == ($totalpages - 1)) {
1154 $start = max(0, $end - $span + 1);
1155 }
1156 }
1157 $items = array();
1158 $baseurl = $this->page->url;
1159 if ($page > 0) {
1160 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
1161 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
1162 $items[] = '...';
1163 }
1164 for ($i = $start; $i <= $end; $i++) {
1165 $class = '';
1166 if ($page == $i) {
1167 $class = 'active-page';
1168 }
1169 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
1170 }
1171 if ($page < ($totalpages - 1)) {
1172 $items[] = '...';
1173 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
1174 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
1175 }
1176
1177 $html .= html_writer::div(join('', $items), 'listing-pagination');
1178 return $html;
1179 }
1180
1181 /**
1182 * Renderers a search result course list item.
1183 *
1184 * This function will be called for every course being displayed by course_listing.
1185 *
9f0595c4 1186 * @param core_course_list_element $course The course to produce HTML for.
5dc361e1
SH
1187 * @param int $selectedcourse The id of the currently selected course.
1188 * @return string
1189 */
442f12f8 1190 public function search_listitem(core_course_list_element $course, $selectedcourse) {
5dc361e1
SH
1191
1192 $text = $course->get_formatted_name();
1193 $attributes = array(
9f0595c4
MM
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'
5dc361e1 1198 );
fe3999ec 1199 $bulkcourseinput = '';
442f12f8 1200 if (core_course_category::get($course->category)->can_move_courses_out_of()) {
fe3999ec 1201 $bulkcourseinput = array(
9f0595c4 1202 'type' => 'checkbox',
a521226d 1203 'id' => 'coursesearchlistitem' . $course->id,
9f0595c4
MM
1204 'name' => 'bc[]',
1205 'value' => $course->id,
a521226d 1206 'class' => 'bulk-action-checkbox custom-control-input',
9f0595c4 1207 'data-action' => 'select'
fe3999ec
WL
1208 );
1209 }
5dc361e1 1210 $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
442f12f8 1211 $categoryname = core_course_category::get($course->category)->get_formatted_name();
5dc361e1
SH
1212
1213 $html = html_writer::start_tag('li', $attributes);
1214 $html .= html_writer::start_div('clearfix');
1215 $html .= html_writer::start_div('float-left');
fe3999ec 1216 if ($bulkcourseinput) {
a521226d
BB
1217 $html .= html_writer::start_div('custom-control custom-checkbox mr-1');
1218 $html .= html_writer::empty_tag('input', $bulkcourseinput);
c3015dd0
JP
1219 $labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
1220 $html .= html_writer::tag('label', $labeltext, array(
a521226d
BB
1221 'class' => 'custom-control-label',
1222 'for' => 'coursesearchlistitem' . $course->id));
1223 $html .= html_writer::end_div();
fe3999ec 1224 }
5dc361e1 1225 $html .= html_writer::end_div();
91de0ed8 1226 $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
a521226d 1227 $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left ml-3 text-muted'));
5dc361e1
SH
1228 $html .= html_writer::start_div('float-right');
1229 $html .= $this->search_listitem_actions($course);
a521226d 1230 $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
5dc361e1
SH
1231 $html .= html_writer::end_div();
1232 $html .= html_writer::end_div();
1233 $html .= html_writer::end_tag('li');
1234 return $html;
1235 }
1236
1237 /**
1238 * Renderers actions for individual course actions.
1239 *
442f12f8 1240 * @param core_course_list_element $course The course to renderer actions for.
5dc361e1
SH
1241 * @return string
1242 */
442f12f8 1243 public function search_listitem_actions(core_course_list_element $course) {
5dc361e1
SH
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.
484c4c6c
SH
1250 if ($course->can_access()) {
1251 if ($course->can_edit()) {
5dc361e1 1252 $actions[] = $this->output->action_icon(
484c4c6c
SH
1253 new moodle_url('/course/edit.php', array('id' => $course->id)),
1254 new pix_icon('t/edit', get_string('edit')),
5dc361e1 1255 null,
484c4c6c 1256 array('class' => 'action-edit')
5dc361e1
SH
1257 );
1258 }
14cea316
WL
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 }
484c4c6c
SH
1268 // Show/Hide.
1269 if ($course->can_change_visibility()) {
484c4c6c
SH
1270 $actions[] = $this->output->action_icon(
1271 new moodle_url($baseurl, array('action' => 'hidecourse')),
617dbe49 1272 new pix_icon('t/hide', get_string('hide')),
484c4c6c
SH
1273 null,
1274 array('data-action' => 'hide', 'class' => 'action-hide')
1275 );
484c4c6c
SH
1276 $actions[] = $this->output->action_icon(
1277 new moodle_url($baseurl, array('action' => 'showcourse')),
617dbe49 1278 new pix_icon('t/show', get_string('show')),
484c4c6c
SH
1279 null,
1280 array('data-action' => 'show', 'class' => 'action-show')
1281 );
484c4c6c 1282 }
5dc361e1
SH
1283 }
1284 if (empty($actions)) {
1285 return '';
1286 }
1287 return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1288 }
1289
4b07116a
WL
1290 /**
1291 * Renders html to display a course search form
1292 *
1293 * @param string $value default value to populate the search field
4b07116a
WL
1294 * @return string
1295 */
d5593b18
BB
1296 public function course_search_form($value = '') {
1297
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);
4b07116a
WL
1307 }
1308
d0647301
SH
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 }
cd96b7d3 1324 if ($displaycourselisting) {
d0647301
SH
1325 $url->set_anchor('course-listing');
1326 $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
1327 }
cd96b7d3 1328 if ($displaycoursedetail) {
d0647301
SH
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 }
1335
9e164027 1336}