Merge branch 'MDL-63303_master-deleteduserfix' of https://github.com/markn86/moodle
[moodle.git] / course / classes / management_renderer.php
blob40bef7524e2da6269000f480569a0ecec0f1d19f
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.
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.
20 * @package core_course
21 * @copyright 2013 Sam Hemelryk
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die;
27 require_once($CFG->dirroot.'/course/renderer.php');
29 /**
30 * Main renderer for the course management pages.
32 * @package core_course
33 * @copyright 2013 Sam Hemelryk
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 class core_course_management_renderer extends plugin_renderer_base {
38 /**
39 * Initialises the JS required to enhance the management interface.
41 * Thunderbirds are go, this function kicks into gear the JS that makes the
42 * course management pages that much cooler.
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'
60 'moodle'
64 /**
65 * Displays a heading for the management pages.
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
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);
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 = '';
87 $select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
88 $html .= $this->render($select);
90 $html .= html_writer::end_div();
92 $html .= html_writer::end_div();
93 return $html;
96 /**
97 * Prepares the form element for the course category listing bulk actions.
99 * @return string
101 public function management_form_start() {
102 $form = array('action' => $this->page->url->out(), 'method' => 'POST', 'id' => 'coursecat-management');
104 $html = html_writer::start_tag('form', $form);
105 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
106 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'bulkaction'));
107 return $html;
111 * Closes the course category bulk management form.
113 * @return string
115 public function management_form_end() {
116 return html_writer::end_tag('form');
120 * Presents a course category listing.
122 * @param core_course_category $category The currently selected category. Also the category to highlight in the listing.
123 * @return string
125 public function category_listing(core_course_category $category = null) {
127 if ($category === null) {
128 $selectedparents = array();
129 $selectedcategory = null;
130 } else {
131 $selectedparents = $category->get_parents();
132 $selectedparents[] = $category->id;
133 $selectedcategory = $category->id;
135 $catatlevel = \core_course\management\helper::get_expanded_categories('');
136 $catatlevel[] = array_shift($selectedparents);
137 $catatlevel = array_unique($catatlevel);
139 $listing = core_course_category::get(0)->get_children();
141 $attributes = array(
142 'class' => 'ml',
143 'role' => 'tree',
144 'aria-labelledby' => 'category-listing-title'
147 $html = html_writer::start_div('category-listing');
148 $html .= html_writer::tag('h3', get_string('categories'), array('id' => 'category-listing-title'));
149 $html .= $this->category_listing_actions($category);
150 $html .= html_writer::start_tag('ul', $attributes);
151 foreach ($listing as $listitem) {
152 // Render each category in the listing.
153 $subcategories = array();
154 if (in_array($listitem->id, $catatlevel)) {
155 $subcategories = $listitem->get_children();
157 $html .= $this->category_listitem(
158 $listitem,
159 $subcategories,
160 $listitem->get_children_count(),
161 $selectedcategory,
162 $selectedparents
165 $html .= html_writer::end_tag('ul');
166 $html .= $this->category_bulk_actions($category);
167 $html .= html_writer::end_div();
168 return $html;
172 * Renders a category list item.
174 * This function gets called recursively to render sub categories.
176 * @param core_course_category $category The category to render as listitem.
177 * @param core_course_category[] $subcategories The subcategories belonging to the category being rented.
178 * @param int $totalsubcategories The total number of sub categories.
179 * @param int $selectedcategory The currently selected category
180 * @param int[] $selectedcategories The path to the selected category and its ID.
181 * @return string
183 public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
184 $selectedcategory = null, $selectedcategories = array()) {
186 $isexpandable = ($totalsubcategories > 0);
187 $isexpanded = (!empty($subcategories));
188 $activecategory = ($selectedcategory === $category->id);
189 $attributes = array(
190 'class' => 'listitem listitem-category',
191 'data-id' => $category->id,
192 'data-expandable' => $isexpandable ? '1' : '0',
193 'data-expanded' => $isexpanded ? '1' : '0',
194 'data-selected' => $activecategory ? '1' : '0',
195 'data-visible' => $category->visible ? '1' : '0',
196 'role' => 'treeitem',
197 'aria-expanded' => $isexpanded ? 'true' : 'false'
199 $text = $category->get_formatted_name();
200 if ($category->parent) {
201 $a = new stdClass;
202 $a->category = $text;
203 $a->parentcategory = $category->get_parent_coursecat()->get_formatted_name();
204 $textlabel = get_string('categorysubcategoryof', 'moodle', $a);
206 $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
207 $bcatinput = array(
208 'type' => 'checkbox',
209 'name' => 'bcat[]',
210 'value' => $category->id,
211 'class' => 'bulk-action-checkbox',
212 'aria-label' => get_string('bulkactionselect', 'moodle', $text),
213 'data-action' => 'select'
216 if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
217 // Very very hardcoded here.
218 $bcatinput['style'] = 'visibility:hidden';
221 $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
222 if ($isexpanded) {
223 $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
224 $icon = html_writer::link(
225 $viewcaturl,
226 $icon,
227 array(
228 'class' => 'float-left',
229 'data-action' => 'collapse',
230 'title' => get_string('collapsecategory', 'moodle', $text),
231 'aria-controls' => 'subcategoryof'.$category->id
234 } else if ($isexpandable) {
235 $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
236 $icon = html_writer::link(
237 $viewcaturl,
238 $icon,
239 array(
240 'class' => 'float-left',
241 'data-action' => 'expand',
242 'title' => get_string('expandcategory', 'moodle', $text)
245 } else {
246 $icon = $this->output->pix_icon(
247 'i/empty',
249 'moodle',
250 array('class' => 'tree-icon'));
251 $icon = html_writer::span($icon, 'float-left');
253 $actions = \core_course\management\helper::get_category_listitem_actions($category);
254 $hasactions = !empty($actions) || $category->can_create_course();
256 $html = html_writer::start_tag('li', $attributes);
257 $html .= html_writer::start_div('clearfix');
258 $html .= html_writer::start_div('float-left ba-checkbox');
259 $html .= html_writer::empty_tag('input', $bcatinput).'&nbsp;';
260 $html .= html_writer::end_div();
261 $html .= $icon;
262 if ($hasactions) {
263 $textattributes = array('class' => 'float-left categoryname');
264 } else {
265 $textattributes = array('class' => 'float-left categoryname without-actions');
267 if (isset($textlabel)) {
268 $textattributes['aria-label'] = $textlabel;
270 $html .= html_writer::link($viewcaturl, $text, $textattributes);
271 $html .= html_writer::start_div('float-right');
272 if ($category->idnumber) {
273 $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
275 if ($hasactions) {
276 $html .= $this->category_listitem_actions($category, $actions);
278 $countid = 'course-count-'.$category->id;
279 $html .= html_writer::span(
280 html_writer::span($category->get_courses_count()) .
281 html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
282 $courseicon,
283 'course-count dimmed',
284 array('aria-labelledby' => $countid)
286 $html .= html_writer::end_div();
287 $html .= html_writer::end_div();
288 if ($isexpanded) {
289 $html .= html_writer::start_tag('ul',
290 array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
291 $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
292 $catatlevel[] = array_shift($selectedcategories);
293 $catatlevel = array_unique($catatlevel);
294 foreach ($subcategories as $listitem) {
295 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
296 $html .= $this->category_listitem(
297 $listitem,
298 $childcategories,
299 $listitem->get_children_count(),
300 $selectedcategory,
301 $selectedcategories
304 $html .= html_writer::end_tag('ul');
306 $html .= html_writer::end_tag('li');
307 return $html;
311 * Renderers the actions that are possible for the course category listing.
313 * These are not the actions associated with an individual category listing.
314 * That happens through category_listitem_actions.
316 * @param core_course_category $category
317 * @return string
319 public function category_listing_actions(core_course_category $category = null) {
320 $actions = array();
322 $cancreatecategory = $category && $category->can_create_subcategory();
323 $cancreatecategory = $cancreatecategory || core_course_category::can_create_top_level_category();
324 if ($category === null) {
325 $category = core_course_category::get(0);
328 if ($cancreatecategory) {
329 $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
330 $actions[] = html_writer::link($url, get_string('createnewcategory'));
332 if (core_course_category::can_approve_course_requests()) {
333 $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
335 if (count($actions) === 0) {
336 return '';
338 return html_writer::div(join(' | ', $actions), 'listing-actions category-listing-actions');
342 * Renderers the actions for individual category list items.
344 * @param core_course_category $category
345 * @param array $actions
346 * @return string
348 public function category_listitem_actions(core_course_category $category, array $actions = null) {
349 if ($actions === null) {
350 $actions = \core_course\management\helper::get_category_listitem_actions($category);
352 $menu = new action_menu();
353 $menu->attributes['class'] .= ' category-item-actions item-actions';
354 $hasitems = false;
355 foreach ($actions as $key => $action) {
356 $hasitems = true;
357 $menu->add(new action_menu_link(
358 $action['url'],
359 $action['icon'],
360 $action['string'],
361 in_array($key, array('show', 'hide', 'moveup', 'movedown')),
362 array('data-action' => $key, 'class' => 'action-'.$key)
365 if (!$hasitems) {
366 return '';
368 return $this->render($menu);
371 public function render_action_menu($menu) {
372 global $OUTPUT;
374 return $OUTPUT->render($menu);
378 * Renders bulk actions for categories.
380 * @param core_course_category $category The currently selected category if there is one.
381 * @return string
383 public function category_bulk_actions(core_course_category $category = null) {
384 // Resort courses.
385 // Change parent.
386 if (!core_course_category::can_resort_any() && !core_course_category::can_change_parent_any()) {
387 return '';
389 $strgo = new lang_string('go');
391 $html = html_writer::start_div('category-bulk-actions bulk-actions');
392 $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
393 if (core_course_category::can_resort_any()) {
394 $selectoptions = array(
395 'selectedcategories' => get_string('selectedcategories'),
396 'allcategories' => get_string('allcategories')
398 $form = html_writer::start_div();
399 if ($category) {
400 $selectoptions = array('thiscategory' => get_string('thiscategory')) + $selectoptions;
401 $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentcategoryid', 'value' => $category->id));
403 $form .= html_writer::div(
404 html_writer::select(
405 $selectoptions,
406 'selectsortby',
407 'selectedcategories',
408 false,
409 array('aria-label' => get_string('selectcategorysort'))
412 $form .= html_writer::div(
413 html_writer::select(
414 array(
415 'name' => get_string('sortbyx', 'moodle', get_string('categoryname')),
416 'namedesc' => get_string('sortbyxreverse', 'moodle', get_string('categoryname')),
417 'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercoursecategory')),
418 'idnumberdesc' => get_string('sortbyxreverse' , 'moodle' , get_string('idnumbercoursecategory')),
419 'none' => get_string('dontsortcategories')
421 'resortcategoriesby',
422 'name',
423 false,
424 array('aria-label' => get_string('selectcategorysortby'), 'class' => 'm-t-1')
427 $form .= html_writer::div(
428 html_writer::select(
429 array(
430 'fullname' => get_string('sortbyx', 'moodle', get_string('fullnamecourse')),
431 'fullnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse')),
432 'shortname' => get_string('sortbyx', 'moodle', get_string('shortnamecourse')),
433 'shortnamedesc' => get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse')),
434 'idnumber' => get_string('sortbyx', 'moodle', get_string('idnumbercourse')),
435 'idnumberdesc' => get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse')),
436 'timecreated' => get_string('sortbyx', 'moodle', get_string('timecreatedcourse')),
437 'timecreateddesc' => get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')),
438 'none' => get_string('dontsortcourses')
440 'resortcoursesby',
441 'fullname',
442 false,
443 array('aria-label' => get_string('selectcoursesortby'), 'class' => 'm-t-1')
446 $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort',
447 'value' => get_string('sort'), 'class' => 'btn btn-secondary m-y-1'));
448 $form .= html_writer::end_div();
450 $html .= html_writer::start_div('detail-pair row yui3-g m-y-1');
451 $html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key span3 col-md-3 yui3-u-1-4');
452 $html .= html_writer::div($form, 'pair-value span9 col-md-9 yui3-u-3-4');
453 $html .= html_writer::end_div();
455 if (core_course_category::can_change_parent_any()) {
456 $options = array();
457 if (has_capability('moodle/category:manage', context_system::instance())) {
458 $options[0] = core_course_category::get(0)->get_formatted_name();
460 $options += core_course_category::make_categories_list('moodle/category:manage');
461 $select = html_writer::select(
462 $options,
463 'movecategoriesto',
465 array('' => 'choosedots'),
466 array('aria-labelledby' => 'moveselectedcategoriesto', 'class' => 'm-r-1')
468 $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'),
469 'class' => 'btn btn-secondary');
470 $html .= $this->detail_pair(
471 html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
472 $select . html_writer::empty_tag('input', $submit)
475 $html .= html_writer::end_div();
476 return $html;
480 * Renders a course listing.
482 * @param core_course_category $category The currently selected category. This is what the listing is focused on.
483 * @param core_course_list_element $course The currently selected course.
484 * @param int $page The page being displayed.
485 * @param int $perpage The number of courses to display per page.
486 * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
487 * @return string
489 public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
490 $page = 0, $perpage = 20,
491 $viewmode = 'default') {
493 if ($category === null) {
494 $html = html_writer::start_div('select-a-category');
495 $html .= html_writer::tag('h3', get_string('courses'),
496 array('id' => 'course-listing-title', 'tabindex' => '0'));
497 $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
498 $html .= html_writer::end_div();
499 return $html;
502 $page = max($page, 0);
503 $perpage = max($perpage, 2);
504 $totalcourses = $category->coursecount;
505 $totalpages = ceil($totalcourses / $perpage);
506 if ($page > $totalpages - 1) {
507 $page = $totalpages - 1;
509 $options = array(
510 'offset' => $page * $perpage,
511 'limit' => $perpage
513 $courseid = isset($course) ? $course->id : null;
514 $class = '';
515 if ($page === 0) {
516 $class .= ' firstpage';
518 if ($page + 1 === (int)$totalpages) {
519 $class .= ' lastpage';
522 $html = html_writer::start_div('course-listing'.$class, array(
523 'data-category' => $category->id,
524 'data-page' => $page,
525 'data-totalpages' => $totalpages,
526 'data-totalcourses' => $totalcourses,
527 'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
529 $html .= html_writer::tag('h3', $category->get_formatted_name(),
530 array('id' => 'course-listing-title', 'tabindex' => '0'));
531 $html .= $this->course_listing_actions($category, $course, $perpage);
532 $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
533 $html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
534 foreach ($category->get_courses($options) as $listitem) {
535 $html .= $this->course_listitem($category, $listitem, $courseid);
537 $html .= html_writer::end_tag('ul');
538 $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
539 $html .= $this->course_bulk_actions($category);
540 $html .= html_writer::end_div();
541 return $html;
545 * Renders pagination for a course listing.
547 * @param core_course_category $category The category to produce pagination for.
548 * @param int $page The current page.
549 * @param int $perpage The number of courses to display per page.
550 * @param bool $showtotals Set to true to show the total number of courses and what is being displayed.
551 * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
552 * @return string
554 protected function listing_pagination(core_course_category $category, $page, $perpage, $showtotals = false,
555 $viewmode = 'default') {
556 $html = '';
557 $totalcourses = $category->get_courses_count();
558 $totalpages = ceil($totalcourses / $perpage);
559 if ($showtotals) {
560 if ($totalpages == 0) {
561 $str = get_string('nocoursesyet');
562 } else if ($totalpages == 1) {
563 $str = get_string('showingacourses', 'moodle', $totalcourses);
564 } else {
565 $a = new stdClass;
566 $a->start = ($page * $perpage) + 1;
567 $a->end = min((($page + 1) * $perpage), $totalcourses);
568 $a->total = $totalcourses;
569 $str = get_string('showingxofycourses', 'moodle', $a);
571 $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
574 if ($viewmode !== 'default') {
575 $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id,
576 'view' => $viewmode));
577 } else {
578 $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
581 $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
582 return $html;
586 * Renderers a course list item.
588 * This function will be called for every course being displayed by course_listing.
590 * @param core_course_category $category The currently selected category and the category the course belongs to.
591 * @param core_course_list_element $course The course to produce HTML for.
592 * @param int $selectedcourse The id of the currently selected course.
593 * @return string
595 public function course_listitem(core_course_category $category, core_course_list_element $course, $selectedcourse) {
597 $text = $course->get_formatted_name();
598 $attributes = array(
599 'class' => 'listitem listitem-course',
600 'data-id' => $course->id,
601 'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
602 'data-visible' => $course->visible ? '1' : '0'
605 $bulkcourseinput = array(
606 'type' => 'checkbox',
607 'name' => 'bc[]',
608 'value' => $course->id,
609 'class' => 'bulk-action-checkbox',
610 'aria-label' => get_string('bulkactionselect', 'moodle', $text),
611 'data-action' => 'select'
613 if (!$category->has_manage_capability()) {
614 // Very very hardcoded here.
615 $bulkcourseinput['style'] = 'visibility:hidden';
618 $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
620 $html = html_writer::start_tag('li', $attributes);
621 $html .= html_writer::start_div('clearfix');
623 if ($category->can_resort_courses()) {
624 // In order for dnd to be available the user must be able to resort the category children..
625 $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
628 $html .= html_writer::start_div('ba-checkbox float-left');
629 $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
630 $html .= html_writer::end_div();
631 $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
632 $html .= html_writer::start_div('float-right');
633 if ($course->idnumber) {
634 $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
636 $html .= $this->course_listitem_actions($category, $course);
637 $html .= html_writer::end_div();
638 $html .= html_writer::end_div();
639 $html .= html_writer::end_tag('li');
640 return $html;
644 * Renderers actions for the course listing.
646 * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
648 * @param core_course_category $category
649 * @param core_course_list_element $course The currently selected course.
650 * @param int $perpage
651 * @return string
653 public function course_listing_actions(core_course_category $category, core_course_list_element $course = null, $perpage = 20) {
654 $actions = array();
655 if ($category->can_create_course()) {
656 $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
657 $actions[] = html_writer::link($url, get_string('createnewcourse'));
659 if ($category->can_request_course()) {
660 // Request a new course.
661 $url = new moodle_url('/course/request.php', array('return' => 'management'));
662 $actions[] = html_writer::link($url, get_string('requestcourse'));
664 if ($category->can_resort_courses()) {
665 $params = $this->page->url->params();
666 $params['action'] = 'resortcourses';
667 $params['sesskey'] = sesskey();
668 $baseurl = new moodle_url('/course/management.php', $params);
669 $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
670 $fullnameurldesc = new moodle_url($baseurl, array('resort' => 'fullnamedesc'));
671 $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
672 $shortnameurldesc = new moodle_url($baseurl, array('resort' => 'shortnamedesc'));
673 $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
674 $idnumberdescurl = new moodle_url($baseurl, array('resort' => 'idnumberdesc'));
675 $timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
676 $timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
677 $menu = new action_menu(array(
678 new action_menu_link_secondary($fullnameurl,
679 null,
680 get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
681 new action_menu_link_secondary($fullnameurldesc,
682 null,
683 get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
684 new action_menu_link_secondary($shortnameurl,
685 null,
686 get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
687 new action_menu_link_secondary($shortnameurldesc,
688 null,
689 get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
690 new action_menu_link_secondary($idnumberurl,
691 null,
692 get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
693 new action_menu_link_secondary($idnumberdescurl,
694 null,
695 get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
696 new action_menu_link_secondary($timecreatedurl,
697 null,
698 get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
699 new action_menu_link_secondary($timecreateddescurl,
700 null,
701 get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
703 $menu->set_menu_trigger(get_string('resortcourses'));
704 $actions[] = $this->render($menu);
706 $strall = get_string('all');
707 $menu = new action_menu(array(
708 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
709 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
710 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
711 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
712 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
713 new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
715 if ((int)$perpage === 999) {
716 $perpage = $strall;
718 $menu->attributes['class'] .= ' courses-per-page';
719 $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
720 $actions[] = $this->render($menu);
721 return html_writer::div(join(' | ', $actions), 'listing-actions course-listing-actions');
725 * Renderers actions for individual course actions.
727 * @param core_course_category $category The currently selected category.
728 * @param core_course_list_element $course The course to renderer actions for.
729 * @return string
731 public function course_listitem_actions(core_course_category $category, core_course_list_element $course) {
732 $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
733 if (empty($actions)) {
734 return '';
736 $actionshtml = array();
737 foreach ($actions as $action) {
738 $action['attributes']['role'] = 'button';
739 $actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
741 return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
745 * Renderers bulk actions that can be performed on courses.
747 * @param core_course_category $category The currently selected category and the category in which courses that
748 * are selectable belong.
749 * @return string
751 public function course_bulk_actions(core_course_category $category) {
752 $html = html_writer::start_div('course-bulk-actions bulk-actions');
753 if ($category->can_move_courses_out_of()) {
754 $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
755 $options = core_course_category::make_categories_list('moodle/category:manage');
756 $select = html_writer::select(
757 $options,
758 'movecoursesto',
760 array('' => 'choosedots'),
761 array('aria-labelledby' => 'moveselectedcoursesto', 'class' => 'm-r-1')
763 $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
764 'class' => 'btn btn-secondary');
765 $html .= $this->detail_pair(
766 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
767 $select . html_writer::empty_tag('input', $submit)
770 $html .= html_writer::end_div();
771 return $html;
775 * Renderers bulk actions that can be performed on courses in search returns
777 * @return string
779 public function course_search_bulk_actions() {
780 $html = html_writer::start_div('course-bulk-actions bulk-actions');
781 $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
782 $options = core_course_category::make_categories_list('moodle/category:manage');
783 $select = html_writer::select(
784 $options,
785 'movecoursesto',
787 array('' => 'choosedots'),
788 array('aria-labelledby' => 'moveselectedcoursesto')
790 $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'),
791 'class' => 'btn btn-secondary');
792 $html .= $this->detail_pair(
793 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
794 $select . html_writer::empty_tag('input', $submit)
796 $html .= html_writer::end_div();
797 return $html;
801 * Renderers detailed course information.
803 * @param core_course_list_element $course The course to display details for.
804 * @return string
806 public function course_detail(core_course_list_element $course) {
807 $details = \core_course\management\helper::get_course_detail_array($course);
808 $fullname = $details['fullname']['value'];
810 $html = html_writer::start_div('course-detail');
811 $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
812 $html .= $this->course_detail_actions($course);
813 foreach ($details as $class => $data) {
814 $html .= $this->detail_pair($data['key'], $data['value'], $class);
816 $html .= html_writer::end_div();
817 return $html;
821 * Renderers a key value pair of information for display.
823 * @param string $key
824 * @param string $value
825 * @param string $class
826 * @return string
828 protected function detail_pair($key, $value, $class ='') {
829 $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
830 $html .= html_writer::div(html_writer::span($key), 'pair-key span3 col-md-3 yui3-u-1-4');
831 $html .= html_writer::div(html_writer::span($value), 'pair-value span9 col-md-9 m-b-1 yui3-u-3-4 form-inline');
832 $html .= html_writer::end_div();
833 return $html;
837 * A collection of actions for a course.
839 * @param core_course_list_element $course The course to display actions for.
840 * @return string
842 public function course_detail_actions(core_course_list_element $course) {
843 $actions = \core_course\management\helper::get_course_detail_actions($course);
844 if (empty($actions)) {
845 return '';
847 $options = array();
848 foreach ($actions as $action) {
849 $options[] = $this->action_link($action['url'], $action['string']);
851 return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
855 * Creates an action button (styled link)
857 * @param moodle_url $url The URL to go to when clicked.
858 * @param string $text The text for the button.
859 * @param string $id An id to give the button.
860 * @param string $class A class to give the button.
861 * @param array $attributes Any additional attributes
862 * @return string
864 protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
865 if (isset($attributes['class'])) {
866 $attributes['class'] .= ' yui3-button';
867 } else {
868 $attributes['class'] = 'yui3-button';
870 if (!is_null($id)) {
871 $attributes['id'] = $id;
873 if (!is_null($class)) {
874 $attributes['class'] .= ' '.$class;
876 if (is_null($title)) {
877 $title = $text;
879 $attributes['title'] = $title;
880 if (!isset($attributes['role'])) {
881 $attributes['role'] = 'button';
883 return html_writer::link($url, $text, $attributes);
887 * Opens a grid.
889 * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
891 * @param string $id An id to give this grid.
892 * @param string $class A class to give this grid.
893 * @return string
895 public function grid_start($id = null, $class = null) {
896 $gridclass = 'grid-row-r row-fluid';
897 if (is_null($class)) {
898 $class = $gridclass;
899 } else {
900 $class .= ' ' . $gridclass;
902 $attributes = array();
903 if (!is_null($id)) {
904 $attributes['id'] = $id;
906 return html_writer::start_div($class, $attributes);
910 * Closes the grid.
912 * @return string
914 public function grid_end() {
915 return html_writer::end_div();
919 * Opens a grid column
921 * @param int $size The number of segments this column should span.
922 * @param string $id An id to give the column.
923 * @param string $class A class to give the column.
924 * @return string
926 public function grid_column_start($size, $id = null, $class = null) {
928 // Calculate Bootstrap grid sizing.
929 $bootstrapclass = 'span'.$size.' col-md-'.$size;
931 // Calculate YUI grid sizing.
932 if ($size === 12) {
933 $maxsize = 1;
934 $size = 1;
935 } else {
936 $maxsize = 12;
937 $divisors = array(8, 6, 5, 4, 3, 2);
938 foreach ($divisors as $divisor) {
939 if (($maxsize % $divisor === 0) && ($size % $divisor === 0)) {
940 $maxsize = $maxsize / $divisor;
941 $size = $size / $divisor;
942 break;
946 if ($maxsize > 1) {
947 $yuigridclass = "grid-col-{$size}-{$maxsize} grid-col";
948 } else {
949 $yuigridclass = "grid-col-1 grid-col";
952 if (is_null($class)) {
953 $class = $yuigridclass . ' ' . $bootstrapclass;
954 } else {
955 $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
957 $attributes = array();
958 if (!is_null($id)) {
959 $attributes['id'] = $id;
961 return html_writer::start_div($class, $attributes);
965 * Closes a grid column.
967 * @return string
969 public function grid_column_end() {
970 return html_writer::end_div();
974 * Renders an action_icon.
976 * This function uses the {@link core_renderer::action_link()} method for the
977 * most part. What it does different is prepare the icon as HTML and use it
978 * as the link text.
980 * @param string|moodle_url $url A string URL or moodel_url
981 * @param pix_icon $pixicon
982 * @param component_action $action
983 * @param array $attributes associative array of html link attributes + disabled
984 * @param bool $linktext show title next to image in link
985 * @return string HTML fragment
987 public function action_icon($url, pix_icon $pixicon, component_action $action = null,
988 array $attributes = null, $linktext = false) {
989 if (!($url instanceof moodle_url)) {
990 $url = new moodle_url($url);
992 $attributes = (array)$attributes;
994 if (empty($attributes['class'])) {
995 // Let devs override the class via $attributes.
996 $attributes['class'] = 'action-icon';
999 $icon = $this->render($pixicon);
1001 if ($linktext) {
1002 $text = $pixicon->attributes['alt'];
1003 } else {
1004 $text = '';
1007 return $this->action_link($url, $icon.$text, $action, $attributes);
1011 * Displays a view mode selector.
1013 * @param array $modes An array of view modes.
1014 * @param string $currentmode The current view mode.
1015 * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
1016 * @param string $param The param name.
1017 * @return string
1019 public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
1020 if ($url === null) {
1021 $url = $this->page->url;
1024 $menu = new action_menu;
1025 $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
1027 $selected = null;
1028 foreach ($modes as $mode => $modestr) {
1029 $attributes = array(
1030 'class' => 'vms-mode',
1031 'data-mode' => $mode
1033 if ($currentmode === $mode) {
1034 $attributes['class'] .= ' currentmode';
1035 $selected = $modestr;
1037 if ($selected === null) {
1038 $selected = $modestr;
1040 $modeurl = new moodle_url($url, array($param => $mode));
1041 if ($mode === 'default') {
1042 $modeurl->remove_params($param);
1044 $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
1047 $menu->set_menu_trigger($selected);
1049 $html = html_writer::start_div('view-mode-selector vms d-flex');
1050 $html .= get_string('viewing').' '.$this->render($menu);
1051 $html .= html_writer::end_div();
1053 return $html;
1057 * Displays a search result listing.
1059 * @param array $courses The courses to display.
1060 * @param int $totalcourses The total number of courses to display.
1061 * @param core_course_list_element $course The currently selected course if there is one.
1062 * @param int $page The current page, starting at 0.
1063 * @param int $perpage The number of courses to display per page.
1064 * @param string $search The string we are searching for.
1065 * @return string
1067 public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
1068 $search = '') {
1069 $page = max($page, 0);
1070 $perpage = max($perpage, 2);
1071 $totalpages = ceil($totalcourses / $perpage);
1072 if ($page > $totalpages - 1) {
1073 $page = $totalpages - 1;
1075 $courseid = isset($course) ? $course->id : null;
1076 $first = true;
1077 $last = false;
1078 $i = $page * $perpage;
1080 $html = html_writer::start_div('course-listing', array(
1081 'data-category' => 'search',
1082 'data-page' => $page,
1083 'data-totalpages' => $totalpages,
1084 'data-totalcourses' => $totalcourses
1086 $html .= html_writer::tag('h3', get_string('courses'));
1087 $html .= $this->search_pagination($totalcourses, $page, $perpage);
1088 $html .= html_writer::start_tag('ul', array('class' => 'ml'));
1089 foreach ($courses as $listitem) {
1090 $i++;
1091 if ($i == $totalcourses) {
1092 $last = true;
1094 $html .= $this->search_listitem($listitem, $courseid, $first, $last);
1095 $first = false;
1097 $html .= html_writer::end_tag('ul');
1098 $html .= $this->search_pagination($totalcourses, $page, $perpage, true, $search);
1099 $html .= $this->course_search_bulk_actions();
1100 $html .= html_writer::end_div();
1101 return $html;
1105 * Displays pagination for search results.
1107 * @param int $totalcourses The total number of courses to be displayed.
1108 * @param int $page The current page.
1109 * @param int $perpage The number of courses being displayed.
1110 * @param bool $showtotals Whether or not to print total information.
1111 * @param string $search The string we are searching for.
1112 * @return string
1114 protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false, $search = '') {
1115 $html = '';
1116 $totalpages = ceil($totalcourses / $perpage);
1117 if ($showtotals) {
1118 if ($totalpages == 0) {
1119 $str = get_string('nocoursesfound', 'moodle', s($search));
1120 } else if ($totalpages == 1) {
1121 $str = get_string('showingacourses', 'moodle', $totalcourses);
1122 } else {
1123 $a = new stdClass;
1124 $a->start = ($page * $perpage) + 1;
1125 $a->end = min((($page + 1) * $perpage), $totalcourses);
1126 $a->total = $totalcourses;
1127 $str = get_string('showingxofycourses', 'moodle', $a);
1129 $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
1132 if ($totalcourses < $perpage) {
1133 return $html;
1135 $aside = 2;
1136 $span = $aside * 2 + 1;
1137 $start = max($page - $aside, 0);
1138 $end = min($page + $aside, $totalpages - 1);
1139 if (($end - $start) < $span) {
1140 if ($start == 0) {
1141 $end = min($totalpages - 1, $span - 1);
1142 } else if ($end == ($totalpages - 1)) {
1143 $start = max(0, $end - $span + 1);
1146 $items = array();
1147 $baseurl = $this->page->url;
1148 if ($page > 0) {
1149 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
1150 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
1151 $items[] = '...';
1153 for ($i = $start; $i <= $end; $i++) {
1154 $class = '';
1155 if ($page == $i) {
1156 $class = 'active-page';
1158 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
1160 if ($page < ($totalpages - 1)) {
1161 $items[] = '...';
1162 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
1163 $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
1166 $html .= html_writer::div(join('', $items), 'listing-pagination');
1167 return $html;
1171 * Renderers a search result course list item.
1173 * This function will be called for every course being displayed by course_listing.
1175 * @param core_course_list_element $course The course to produce HTML for.
1176 * @param int $selectedcourse The id of the currently selected course.
1177 * @return string
1179 public function search_listitem(core_course_list_element $course, $selectedcourse) {
1181 $text = $course->get_formatted_name();
1182 $attributes = array(
1183 'class' => 'listitem listitem-course',
1184 'data-id' => $course->id,
1185 'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
1186 'data-visible' => $course->visible ? '1' : '0'
1188 $bulkcourseinput = '';
1189 if (core_course_category::get($course->category)->can_move_courses_out_of()) {
1190 $bulkcourseinput = array(
1191 'type' => 'checkbox',
1192 'name' => 'bc[]',
1193 'value' => $course->id,
1194 'class' => 'bulk-action-checkbox',
1195 'aria-label' => get_string('bulkactionselect', 'moodle', $text),
1196 'data-action' => 'select'
1199 $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
1200 $categoryname = core_course_category::get($course->category)->get_formatted_name();
1202 $html = html_writer::start_tag('li', $attributes);
1203 $html .= html_writer::start_div('clearfix');
1204 $html .= html_writer::start_div('float-left');
1205 if ($bulkcourseinput) {
1206 $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
1208 $html .= html_writer::end_div();
1209 $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
1210 $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left categoryname'));
1211 $html .= html_writer::start_div('float-right');
1212 $html .= $this->search_listitem_actions($course);
1213 $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
1214 $html .= html_writer::end_div();
1215 $html .= html_writer::end_div();
1216 $html .= html_writer::end_tag('li');
1217 return $html;
1221 * Renderers actions for individual course actions.
1223 * @param core_course_list_element $course The course to renderer actions for.
1224 * @return string
1226 public function search_listitem_actions(core_course_list_element $course) {
1227 $baseurl = new moodle_url(
1228 '/course/managementsearch.php',
1229 array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
1231 $actions = array();
1232 // Edit.
1233 if ($course->can_access()) {
1234 if ($course->can_edit()) {
1235 $actions[] = $this->output->action_icon(
1236 new moodle_url('/course/edit.php', array('id' => $course->id)),
1237 new pix_icon('t/edit', get_string('edit')),
1238 null,
1239 array('class' => 'action-edit')
1242 // Delete.
1243 if ($course->can_delete()) {
1244 $actions[] = $this->output->action_icon(
1245 new moodle_url('/course/delete.php', array('id' => $course->id)),
1246 new pix_icon('t/delete', get_string('delete')),
1247 null,
1248 array('class' => 'action-delete')
1251 // Show/Hide.
1252 if ($course->can_change_visibility()) {
1253 $actions[] = $this->output->action_icon(
1254 new moodle_url($baseurl, array('action' => 'hidecourse')),
1255 new pix_icon('t/hide', get_string('hide')),
1256 null,
1257 array('data-action' => 'hide', 'class' => 'action-hide')
1259 $actions[] = $this->output->action_icon(
1260 new moodle_url($baseurl, array('action' => 'showcourse')),
1261 new pix_icon('t/show', get_string('show')),
1262 null,
1263 array('data-action' => 'show', 'class' => 'action-show')
1267 if (empty($actions)) {
1268 return '';
1270 return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1274 * Renders html to display a course search form
1276 * @param string $value default value to populate the search field
1277 * @param string $format display format - 'plain' (default), 'short' or 'navbar'
1278 * @return string
1280 public function course_search_form($value = '', $format = 'plain') {
1281 static $count = 0;
1282 $formid = 'coursesearch';
1283 if ((++$count) > 1) {
1284 $formid .= $count;
1287 switch ($format) {
1288 case 'navbar' :
1289 $formid = 'coursesearchnavbar';
1290 $inputid = 'navsearchbox';
1291 $inputsize = 20;
1292 break;
1293 case 'short' :
1294 $inputid = 'shortsearchbox';
1295 $inputsize = 12;
1296 break;
1297 default :
1298 $inputid = 'coursesearchbox';
1299 $inputsize = 30;
1302 $strsearchcourses = get_string("searchcourses");
1303 $searchurl = new moodle_url('/course/management.php');
1305 $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get',
1306 'class' => 'form-inline'));
1307 $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset m-y-1'));
1308 $output .= html_writer::tag('label', $strsearchcourses, array('for' => $inputid));
1309 $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid, 'size' => $inputsize,
1310 'name' => 'search', 'value' => s($value), 'class' => 'form-control m-x-1'));
1311 $output .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'),
1312 'class' => 'btn btn-secondary'));
1313 $output .= html_writer::end_tag('fieldset');
1314 $output .= html_writer::end_tag('form');
1316 return $output;
1320 * Creates access hidden skip to links for the displayed sections.
1322 * @param bool $displaycategorylisting
1323 * @param bool $displaycourselisting
1324 * @param bool $displaycoursedetail
1325 * @return string
1327 public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
1328 $html = html_writer::start_div('skiplinks accesshide');
1329 $url = new moodle_url($this->page->url);
1330 if ($displaycategorylisting) {
1331 $url->set_anchor('category-listing');
1332 $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
1334 if ($displaycourselisting) {
1335 $url->set_anchor('course-listing');
1336 $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
1338 if ($displaycoursedetail) {
1339 $url->set_anchor('course-detail');
1340 $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
1342 $html .= html_writer::end_div();
1343 return $html;