Merge branch 'mdl-46427-master' of git://github.com/deraadt/moodle
[moodle.git] / course / classes / management_renderer.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Contains renderers for the course management pages.
19  *
20  * @package core_course
21  * @copyright 2013 Sam Hemelryk
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die;
27 require_once($CFG->dirroot.'/course/renderer.php');
29 /**
30  * Main renderer for the course management pages.
31  *
32  * @package core_course
33  * @copyright 2013 Sam Hemelryk
34  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class core_course_management_renderer extends plugin_renderer_base {
38     /**
39      * Initialises the JS required to enhance the management interface.
40      *
41      * Thunderbirds are go, this function kicks into gear the JS that makes the
42      * course management pages that much cooler.
43      */
44     public function enhance_management_interface() {
45         $this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
46         $this->page->requires->strings_for_js(
47             array(
48                 'show',
49                 'showcategory',
50                 'hide',
51                 'expand',
52                 'expandcategory',
53                 'collapse',
54                 'collapsecategory',
55                 'confirmcoursemove',
56                 'move',
57                 'cancel',
58                 'confirm'
59             ),
60             'moodle'
61         );
62     }
64     /**
65      * Displays a heading for the management pages.
66      *
67      * @param string $heading The heading to display
68      * @param string|null $viewmode The current view mode if there are options.
69      * @param int|null $categoryid The currently selected category if there is one.
70      * @return string
71      */
72     public function management_heading($heading, $viewmode = null, $categoryid = null) {
73         $html = html_writer::start_div('coursecat-management-header clearfix');
74         if (!empty($heading)) {
75             $html .= $this->heading($heading);
76         }
77         if ($viewmode !== null) {
78             $html .= html_writer::start_div();
79             $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
80             if ($viewmode === 'courses') {
81                 $categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
82                 $nothing = false;
83                 if ($categoryid === null) {
84                     $nothing = array('' => get_string('selectacategory'));
85                     $categoryid = '';
86                 }
87                 $select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
88                 $html .= $this->render($select);
89             }
90             $html .= html_writer::end_div();
91         }
92         $html .= html_writer::end_div();
93         return $html;
94     }
96     /**
97      * Prepares the form element for the course category listing bulk actions.
98      *
99      * @return string
100      */
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;
108     }
110     /**
111      * Closes the course category bulk management form.
112      *
113      * @return string
114      */
115     public function management_form_end() {
116         return html_writer::end_tag('form');
117     }
119     /**
120      * Presents a course category listing.
121      *
122      * @param coursecat $category The currently selected category. Also the category to highlight in the listing.
123      * @return string
124      */
125     public function category_listing(coursecat $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;
134         }
135         $catatlevel = \core_course\management\helper::get_expanded_categories('');
136         $catatlevel[] = array_shift($selectedparents);
137         $catatlevel = array_unique($catatlevel);
139         $listing = coursecat::get(0)->get_children();
141         $attributes = array(
142             'class' => 'ml',
143             'role' => 'tree',
144             'aria-labelledby' => 'category-listing-title'
145         );
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();
156             }
157             $html .= $this->category_listitem(
158                 $listitem,
159                 $subcategories,
160                 $listitem->get_children_count(),
161                 $selectedcategory,
162                 $selectedparents
163             );
164         }
165         $html .= html_writer::end_tag('ul');
166         $html .= $this->category_bulk_actions($category);
167         $html .= html_writer::end_div();
168         return $html;
169     }
171     /**
172      * Renders a category list item.
173      *
174      * This function gets called recursively to render sub categories.
175      *
176      * @param coursecat $category The category to render as listitem.
177      * @param coursecat[] $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
182      */
183     public function category_listitem(coursecat $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'
198         );
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);
205         }
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'
214         );
216         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
217             // Very very hardcoded here.
218             $bcatinput['style'] = 'visibility:hidden';
219         }
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
232                 )
233             );
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)
243                 )
244             );
245         } else {
246             $icon = $this->output->pix_icon(
247                 'i/navigationitem',
248                 '',
249                 'moodle',
250                 array('class' => 'tree-icon', 'title' => get_string('showcategory', 'moodle', $text))
251             );
252             $icon = html_writer::span($icon, 'float-left');
253         }
254         $actions = \core_course\management\helper::get_category_listitem_actions($category);
255         $hasactions = !empty($actions) || $category->can_create_course();
257         $html = html_writer::start_tag('li', $attributes);
258         $html .= html_writer::start_div('clearfix');
259         $html .= html_writer::start_div('float-left ba-checkbox');
260         $html .= html_writer::empty_tag('input', $bcatinput).'&nbsp;';
261         $html .= html_writer::end_div();
262         $html .= $icon;
263         if ($hasactions) {
264             $textattributes = array('class' => 'float-left categoryname');
265         } else {
266             $textattributes = array('class' => 'float-left categoryname without-actions');
267         }
268         if (isset($textlabel)) {
269             $textattributes['aria-label'] = $textlabel;
270         }
271         $html .= html_writer::link($viewcaturl, $text, $textattributes);
272         $html .= html_writer::start_div('float-right');
273         if ($category->idnumber) {
274             $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
275         }
276         if ($hasactions) {
277             $html .= $this->category_listitem_actions($category, $actions);
278         }
279         $countid = 'course-count-'.$category->id;
280         $html .= html_writer::span(
281             html_writer::span($category->get_courses_count()) .
282             html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
283             $courseicon,
284             'course-count dimmed',
285             array('aria-labelledby' => $countid)
286         );
287         $html .= html_writer::end_div();
288         $html .= html_writer::end_div();
289         if ($isexpanded) {
290             $html .= html_writer::start_tag('ul',
291                 array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
292             $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
293             $catatlevel[] = array_shift($selectedcategories);
294             $catatlevel = array_unique($catatlevel);
295             foreach ($subcategories as $listitem) {
296                 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
297                 $html .= $this->category_listitem(
298                     $listitem,
299                     $childcategories,
300                     $listitem->get_children_count(),
301                     $selectedcategory,
302                     $selectedcategories
303                 );
304             }
305             $html .= html_writer::end_tag('ul');
306         }
307         $html .= html_writer::end_tag('li');
308         return $html;
309     }
311     /**
312      * Renderers the actions that are possible for the course category listing.
313      *
314      * These are not the actions associated with an individual category listing.
315      * That happens through category_listitem_actions.
316      *
317      * @param coursecat $category
318      * @return string
319      */
320     public function category_listing_actions(coursecat $category = null) {
321         $actions = array();
323         $cancreatecategory = $category && $category->can_create_subcategory();
324         $cancreatecategory = $cancreatecategory || coursecat::can_create_top_level_category();
325         if ($category === null) {
326             $category = coursecat::get(0);
327         }
329         if ($cancreatecategory) {
330             $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
331             $actions[] = html_writer::link($url, get_string('createnewcategory'));
332         }
333         if (coursecat::can_approve_course_requests()) {
334             $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
335         }
336         if (count($actions) === 0) {
337             return '';
338         }
339         return html_writer::div(join(' | ', $actions), 'listing-actions category-listing-actions');
340     }
342     /**
343      * Renderers the actions for individual category list items.
344      *
345      * @param coursecat $category
346      * @param array $actions
347      * @return string
348      */
349     public function category_listitem_actions(coursecat $category, array $actions = null) {
350         if ($actions === null) {
351             $actions = \core_course\management\helper::get_category_listitem_actions($category);
352         }
353         $menu = new action_menu();
354         $menu->attributes['class'] .= ' category-item-actions item-actions';
355         $hasitems = false;
356         foreach ($actions as $key => $action) {
357             $hasitems = true;
358             $menu->add(new action_menu_link(
359                 $action['url'],
360                 $action['icon'],
361                 $action['string'],
362                 in_array($key, array('show', 'hide', 'moveup', 'movedown')),
363                 array('data-action' => $key, 'class' => 'action-'.$key)
364             ));
365         }
366         if (!$hasitems) {
367             return '';
368         }
369         return $this->render($menu);
370     }
372     /**
373      * Renders bulk actions for categories.
374      *
375      * @param coursecat $category The currently selected category if there is one.
376      * @return string
377      */
378     public function category_bulk_actions(coursecat $category = null) {
379         // Resort courses.
380         // Change parent.
381         if (!coursecat::can_resort_any() && !coursecat::can_change_parent_any()) {
382             return '';
383         }
384         $strgo = new lang_string('go');
386         $html  = html_writer::start_div('category-bulk-actions bulk-actions');
387         $html .= html_writer::div(get_string('categorybulkaction'), 'accesshide', array('tabindex' => '0'));
388         if (coursecat::can_resort_any()) {
389             $selectoptions = array(
390                 'selectedcategories' => get_string('selectedcategories'),
391                 'allcategories' => get_string('allcategories')
392             );
393             $form = html_writer::start_div();
394             if ($category) {
395                 $selectoptions = array('thiscategory' => get_string('thiscategory')) + $selectoptions;
396                 $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentcategoryid', 'value' => $category->id));
397             }
398             $form .= html_writer::div(
399                 html_writer::select(
400                     $selectoptions,
401                     'selectsortby',
402                     'selectedcategories',
403                     false,
404                     array('aria-label' => get_string('selectcategorysort'))
405                 )
406             );
407             $form .= html_writer::div(
408                 html_writer::select(
409                     array(
410                         'name' => get_string('sortcategoriesbyname'),
411                         'idnumber' => get_string('sortcategoriesbyidnumber'),
412                         'none' => get_string('dontsortcategories')
413                     ),
414                     'resortcategoriesby',
415                     'name',
416                     false,
417                     array('aria-label' => get_string('selectcategorysortby'))
418                 )
419             );
420             $form .= html_writer::div(
421                 html_writer::select(
422                     array(
423                         'fullname' => get_string('sortcoursesbyfullname'),
424                         'shortname' => get_string('sortcoursesbyshortname'),
425                         'idnumber' => get_string('sortcoursesbyidnumber'),
426                         'none' => get_string('dontsortcourses')
427                     ),
428                     'resortcoursesby',
429                     'fullname',
430                     false,
431                     array('aria-label' => get_string('selectcoursesortby'))
432                 )
433             );
434             $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort', 'value' => get_string('sort')));
435             $form .= html_writer::end_div();
437             $html .= html_writer::start_div('detail-pair row yui3-g');
438             $html .= html_writer::div(html_writer::span(get_string('sorting')), 'pair-key span3 yui3-u-1-4');
439             $html .= html_writer::div($form, 'pair-value span9 yui3-u-3-4');
440             $html .= html_writer::end_div();
441         }
442         if (coursecat::can_change_parent_any()) {
443             $options = array();
444             if (has_capability('moodle/category:manage', context_system::instance())) {
445                 $options[0] = coursecat::get(0)->get_formatted_name();
446             }
447             $options += coursecat::make_categories_list('moodle/category:manage');
448             $select = html_writer::select(
449                 $options,
450                 'movecategoriesto',
451                 '',
452                 array('' => 'choosedots'),
453                 array('aria-labelledby' => 'moveselectedcategoriesto')
454             );
455             $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'));
456             $html .= $this->detail_pair(
457                 html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
458                 $select . html_writer::empty_tag('input', $submit)
459             );
460         }
461         $html .= html_writer::end_div();
462         return $html;
463     }
465     /**
466      * Renders a course listing.
467      *
468      * @param coursecat $category The currently selected category. This is what the listing is focused on.
469      * @param course_in_list $course The currently selected course.
470      * @param int $page The page being displayed.
471      * @param int $perpage The number of courses to display per page.
472      * @return string
473      */
474     public function course_listing(coursecat $category = null, course_in_list $course = null, $page = 0, $perpage = 20) {
476         if ($category === null) {
477             $html = html_writer::start_div('select-a-category');
478             $html .= html_writer::tag('h3', get_string('courses'),
479                 array('id' => 'course-listing-title', 'tabindex' => '0'));
480             $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
481             $html .= html_writer::end_div();
482             return $html;
483         }
485         $page = max($page, 0);
486         $perpage = max($perpage, 2);
487         $totalcourses = $category->coursecount;
488         $totalpages = ceil($totalcourses / $perpage);
489         if ($page > $totalpages - 1) {
490             $page = $totalpages - 1;
491         }
492         $options = array(
493             'offset' => $page * $perpage,
494             'limit' => $perpage
495         );
496         $courseid = isset($course) ? $course->id : null;
497         $class = '';
498         if ($page === 0) {
499             $class .= ' firstpage';
500         }
501         if ($page + 1 === (int)$totalpages) {
502             $class .= ' lastpage';
503         }
505         $html  = html_writer::start_div('course-listing'.$class, array(
506             'data-category' => $category->id,
507             'data-page' => $page,
508             'data-totalpages' => $totalpages,
509             'data-totalcourses' => $totalcourses,
510             'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
511         ));
512         $html .= html_writer::tag('h3', $category->get_formatted_name(),
513             array('id' => 'course-listing-title', 'tabindex' => '0'));
514         $html .= $this->course_listing_actions($category, $course, $perpage);
515         $html .= $this->listing_pagination($category, $page, $perpage);
516         $html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
517         foreach ($category->get_courses($options) as $listitem) {
518             $html .= $this->course_listitem($category, $listitem, $courseid);
519         }
520         $html .= html_writer::end_tag('ul');
521         $html .= $this->listing_pagination($category, $page, $perpage, true);
522         $html .= $this->course_bulk_actions($category);
523         $html .= html_writer::end_div();
524         return $html;
525     }
527     /**
528      * Renders pagination for a course listing.
529      *
530      * @param coursecat $category The category to produce pagination for.
531      * @param int $page The current page.
532      * @param int $perpage The number of courses to display per page.
533      * @param bool $showtotals Set to true to show the total number of courses and what is being displayed.
534      * @return string
535      */
536     protected function listing_pagination(coursecat $category, $page, $perpage, $showtotals = false) {
537         $html = '';
538         $totalcourses = $category->get_courses_count();
539         $totalpages = ceil($totalcourses / $perpage);
540         if ($showtotals) {
541             if ($totalpages == 0) {
542                 $str = get_string('nocoursesyet');
543             } else if ($totalpages == 1) {
544                 $str = get_string('showingacourses', 'moodle', $totalcourses);
545             } else {
546                 $a = new stdClass;
547                 $a->start = ($page * $perpage) + 1;
548                 $a->end = min((($page + 1) * $perpage), $totalcourses);
549                 $a->total = $totalcourses;
550                 $str = get_string('showingxofycourses', 'moodle', $a);
551             }
552             $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
553         }
555         if ($totalcourses <= $perpage) {
556             return $html;
557         }
558         $aside = 2;
559         $span = $aside * 2 + 1;
560         $start = max($page - $aside, 0);
561         $end = min($page + $aside, $totalpages - 1);
562         if (($end - $start) < $span) {
563             if ($start == 0) {
564                 $end = min($totalpages - 1, $span - 1);
565             } else if ($end == ($totalpages - 1)) {
566                 $start = max(0, $end - $span + 1);
567             }
568         }
569         $items = array();
570         $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
571         if ($page > 0) {
572             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
573             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
574             $items[] = '...';
575         }
576         for ($i = $start; $i <= $end; $i++) {
577             $class = '';
578             if ($page == $i) {
579                 $class = 'active-page';
580             }
581             $pageurl = new moodle_url($baseurl, array('page' => $i));
582             $items[] = $this->action_button($pageurl, $i + 1, null, $class, get_string('pagea', 'moodle', $i+1));
583         }
584         if ($page < ($totalpages - 1)) {
585             $items[] = '...';
586             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
587             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
588         }
590         $html .= html_writer::div(join('', $items), 'listing-pagination');
591         return $html;
592     }
594     /**
595      * Renderers a course list item.
596      *
597      * This function will be called for every course being displayed by course_listing.
598      *
599      * @param coursecat $category The currently selected category and the category the course belongs to.
600      * @param course_in_list $course The course to produce HTML for.
601      * @param int $selectedcourse The id of the currently selected course.
602      * @return string
603      */
604     public function course_listitem(coursecat $category, course_in_list $course, $selectedcourse) {
606         $text = $course->get_formatted_name();
607         $attributes = array(
608             'class' => 'listitem listitem-course',
609             'data-id' => $course->id,
610             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
611             'data-visible' => $course->visible ? '1' : '0'
612         );
614         $bulkcourseinput = array(
615             'type' => 'checkbox',
616             'name' => 'bc[]',
617             'value' => $course->id,
618             'class' => 'bulk-action-checkbox',
619             'aria-label' => get_string('bulkactionselect', 'moodle', $text),
620             'data-action' => 'select'
621         );
622         if (!$category->has_manage_capability()) {
623             // Very very hardcoded here.
624             $bulkcourseinput['style'] = 'visibility:hidden';
625         }
627         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
629         $html  = html_writer::start_tag('li', $attributes);
630         $html .= html_writer::start_div('clearfix');
632         if ($category->can_resort_courses()) {
633             // In order for dnd to be available the user must be able to resort the category children..
634             $html .= html_writer::div($this->output->pix_icon('i/move_2d', get_string('dndcourse')), 'float-left drag-handle');
635         }
637         $html .= html_writer::start_div('ba-checkbox float-left');
638         $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
639         $html .= html_writer::end_div();
640         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
641         $html .= html_writer::start_div('float-right');
642         if ($course->idnumber) {
643             $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
644         }
645         $html .= $this->course_listitem_actions($category, $course);
646         $html .= html_writer::end_div();
647         $html .= html_writer::end_div();
648         $html .= html_writer::end_tag('li');
649         return $html;
650     }
652     /**
653      * Renderers actions for the course listing.
654      *
655      * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
656      *
657      * @param coursecat $category
658      * @param course_in_list $course The currently selected course.
659      * @param int $perpage
660      * @return string
661      */
662     public function course_listing_actions(coursecat $category, course_in_list $course = null, $perpage = 20) {
663         $actions = array();
664         if ($category->can_create_course()) {
665             $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
666             $actions[] = html_writer::link($url, get_string('createnewcourse'));
667         }
668         if ($category->can_request_course()) {
669             // Request a new course.
670             $url = new moodle_url('/course/request.php', array('return' => 'management'));
671             $actions[] = html_writer::link($url, get_string('requestcourse'));
672         }
673         if ($category->can_resort_courses()) {
674             $params = $this->page->url->params();
675             $params['action'] = 'resortcourses';
676             $params['sesskey'] = sesskey();
677             $baseurl = new moodle_url('/course/management.php', $params);
678             $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
679             $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
680             $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
681             $menu = new action_menu(array(
682                 new action_menu_link_secondary($fullnameurl, null, get_string('resortbyfullname')),
683                 new action_menu_link_secondary($shortnameurl, null, get_string('resortbyshortname')),
684                 new action_menu_link_secondary($idnumberurl, null, get_string('resortbyidnumber'))
685             ));
686             $menu->set_menu_trigger(get_string('resortcourses'));
687             $actions[] = $this->render($menu);
688         }
689         $strall = get_string('all');
690         $menu = new action_menu(array(
691             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
692             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
693             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
694             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
695             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
696             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
697         ));
698         if ((int)$perpage === 999) {
699             $perpage = $strall;
700         }
701         $menu->attributes['class'] .= ' courses-per-page';
702         $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
703         $actions[] = $this->render($menu);
704         return html_writer::div(join(' | ', $actions), 'listing-actions course-listing-actions');
705     }
707     /**
708      * Renderers actions for individual course actions.
709      *
710      * @param coursecat $category The currently selected category.
711      * @param course_in_list $course The course to renderer actions for.
712      * @return string
713      */
714     public function course_listitem_actions(coursecat $category, course_in_list $course) {
715         $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
716         if (empty($actions)) {
717             return '';
718         }
719         $actionshtml = array();
720         foreach ($actions as $action) {
721             $action['attributes']['role'] = 'button';
722             $actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
723         }
724         return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
725     }
727     /**
728      * Renderers bulk actions that can be performed on courses.
729      *
730      * @param coursecat $category The currently selected category and the category in which courses that
731      *      are selectable belong.
732      * @return string
733      */
734     public function course_bulk_actions(coursecat $category) {
735         $html  = html_writer::start_div('course-bulk-actions bulk-actions');
736         if ($category->can_move_courses_out_of()) {
737             $html .= html_writer::div(get_string('coursebulkaction'), 'accesshide', array('tabindex' => '0'));
738             $options = coursecat::make_categories_list('moodle/category:manage');
739             $select = html_writer::select(
740                 $options,
741                 'movecoursesto',
742                 '',
743                 array('' => 'choosedots'),
744                 array('aria-labelledby' => 'moveselectedcoursesto')
745             );
746             $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'));
747             $html .= $this->detail_pair(
748                 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
749                 $select . html_writer::empty_tag('input', $submit)
750             );
751         }
752         $html .= html_writer::end_div();
753         return $html;
754     }
756     /**
757      * Renderers detailed course information.
758      *
759      * @param course_in_list $course The course to display details for.
760      * @return string
761      */
762     public function course_detail(course_in_list $course) {
763         $details = \core_course\management\helper::get_course_detail_array($course);
764         $fullname = $details['fullname']['value'];
766         $html  = html_writer::start_div('course-detail');
767         $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
768         $html .= $this->course_detail_actions($course);
769         foreach ($details as $class => $data) {
770             $html .= $this->detail_pair($data['key'], $data['value'], $class);
771         }
772         $html .= html_writer::end_div();
773         return $html;
774     }
776     /**
777      * Renderers a key value pair of information for display.
778      *
779      * @param string $key
780      * @param string $value
781      * @param string $class
782      * @return string
783      */
784     protected function detail_pair($key, $value, $class ='') {
785         $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
786         $html .= html_writer::div(html_writer::span($key), 'pair-key span3 yui3-u-1-4');
787         $html .= html_writer::div(html_writer::span($value), 'pair-value span9 yui3-u-3-4');
788         $html .= html_writer::end_div();
789         return $html;
790     }
792     /**
793      * A collection of actions for a course.
794      *
795      * @param course_in_list $course The course to display actions for.
796      * @return string
797      */
798     public function course_detail_actions(course_in_list $course) {
799         $actions = \core_course\management\helper::get_course_detail_actions($course);
800         if (empty($actions)) {
801             return '';
802         }
803         $options = array();
804         foreach ($actions as $action) {
805             $options[] = $this->action_link($action['url'], $action['string']);
806         }
807         return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
808     }
810     /**
811      * Creates an action button (styled link)
812      *
813      * @param moodle_url $url The URL to go to when clicked.
814      * @param string $text The text for the button.
815      * @param string $id An id to give the button.
816      * @param string $class A class to give the button.
817      * @param array $attributes Any additional attributes
818      * @return string
819      */
820     protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null, array $attributes = array()) {
821         if (isset($attributes['class'])) {
822             $attributes['class'] .= ' yui3-button';
823         } else {
824             $attributes['class'] = 'yui3-button';
825         }
826         if (!is_null($id)) {
827             $attributes['id'] = $id;
828         }
829         if (!is_null($class)) {
830             $attributes['class'] .= ' '.$class;
831         }
832         if (is_null($title)) {
833             $title = $text;
834         }
835         $attributes['title'] = $title;
836         if (!isset($attributes['role'])) {
837             $attributes['role'] = 'button';
838         }
839         return html_writer::link($url, $text, $attributes);
840     }
842     /**
843      * Opens a grid.
844      *
845      * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
846      *
847      * @param string $id An id to give this grid.
848      * @param string $class A class to give this grid.
849      * @return string
850      */
851     public function grid_start($id = null, $class = null) {
852         $gridclass = 'grid-row-r row-fluid';
853         if (is_null($class)) {
854             $class = $gridclass;
855         } else {
856             $class .= ' ' . $gridclass;
857         }
858         $attributes = array();
859         if (!is_null($id)) {
860             $attributes['id'] = $id;
861         }
862         return html_writer::start_div($class, $attributes);
863     }
865     /**
866      * Closes the grid.
867      *
868      * @return string
869      */
870     public function grid_end() {
871         return html_writer::end_div();
872     }
874     /**
875      * Opens a grid column
876      *
877      * @param int $size The number of segments this column should span.
878      * @param string $id An id to give the column.
879      * @param string $class A class to give the column.
880      * @return string
881      */
882     public function grid_column_start($size, $id = null, $class = null) {
884         // Calculate Bootstrap grid sizing.
885         $bootstrapclass = 'span'.$size;
887         // Calculate YUI grid sizing.
888         if ($size === 12) {
889             $maxsize = 1;
890             $size = 1;
891         } else {
892             $maxsize = 12;
893             $divisors = array(8, 6, 5, 4, 3, 2);
894             foreach ($divisors as $divisor) {
895                 if (($maxsize % $divisor === 0) && ($size % $divisor === 0)) {
896                     $maxsize = $maxsize / $divisor;
897                     $size = $size / $divisor;
898                     break;
899                 }
900             }
901         }
902         if ($maxsize > 1) {
903             $yuigridclass =  "grid-col-{$size}-{$maxsize} grid-col";
904         } else {
905             $yuigridclass =  "grid-col-1 grid-col";
906         }
908         if (is_null($class)) {
909             $class = $yuigridclass . ' ' . $bootstrapclass;
910         } else {
911             $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
912         }
913         $attributes = array();
914         if (!is_null($id)) {
915             $attributes['id'] = $id;
916         }
917         return html_writer::start_div($class, $attributes);
918     }
920     /**
921      * Closes a grid column.
922      *
923      * @return string
924      */
925     public function grid_column_end() {
926         return html_writer::end_div();
927     }
929     /**
930      * Renders an action_icon.
931      *
932      * This function uses the {@link core_renderer::action_link()} method for the
933      * most part. What it does different is prepare the icon as HTML and use it
934      * as the link text.
935      *
936      * @param string|moodle_url $url A string URL or moodel_url
937      * @param pix_icon $pixicon
938      * @param component_action $action
939      * @param array $attributes associative array of html link attributes + disabled
940      * @param bool $linktext show title next to image in link
941      * @return string HTML fragment
942      */
943     public function action_icon($url, pix_icon $pixicon, component_action $action = null,
944                                 array $attributes = null, $linktext = false) {
945         if (!($url instanceof moodle_url)) {
946             $url = new moodle_url($url);
947         }
948         $attributes = (array)$attributes;
950         if (empty($attributes['class'])) {
951             // Let devs override the class via $attributes.
952             $attributes['class'] = 'action-icon';
953         }
955         $icon = $this->render($pixicon);
957         if ($linktext) {
958             $text = $pixicon->attributes['alt'];
959         } else {
960             $text = '';
961         }
963         return $this->action_link($url, $icon.$text, $action, $attributes);
964     }
966     /**
967      * Displays a view mode selector.
968      *
969      * @param array $modes An array of view modes.
970      * @param string $currentmode The current view mode.
971      * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
972      * @param string $param The param name.
973      * @return string
974      */
975     public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
976         if ($url === null) {
977             $url = $this->page->url;
978         }
980         $menu = new action_menu;
981         $menu->attributes['class'] .= ' view-mode-selector vms';
983         $selected = null;
984         foreach ($modes as $mode => $modestr) {
985             $attributes = array(
986                 'class' => 'vms-mode',
987                 'data-mode' => $mode
988             );
989             if ($currentmode === $mode) {
990                 $attributes['class'] .= ' currentmode';
991                 $selected = $modestr;
992             }
993             if ($selected === null) {
994                 $selected = $modestr;
995             }
996             $modeurl = new moodle_url($url, array($param => $mode));
997             if ($mode === 'default') {
998                 $modeurl->remove_params($param);
999             }
1000             $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
1001         }
1003         $menu->set_menu_trigger($selected);
1005         $html = html_writer::start_div('view-mode-selector vms');
1006         $html .= get_string('viewing').' '.$this->render($menu);
1007         $html .= html_writer::end_div();
1009         return $html;
1010     }
1012     /**
1013      * Displays a search result listing.
1014      *
1015      * @param array $courses The courses to display.
1016      * @param int $totalcourses The total number of courses to display.
1017      * @param course_in_list $course The currently selected course if there is one.
1018      * @param int $page The current page, starting at 0.
1019      * @param int $perpage The number of courses to display per page.
1020      * @return string
1021      */
1022     public function search_listing(array $courses, $totalcourses, course_in_list $course = null, $page = 0, $perpage = 20) {
1023         $page = max($page, 0);
1024         $perpage = max($perpage, 2);
1025         $totalpages = ceil($totalcourses / $perpage);
1026         if ($page > $totalpages - 1) {
1027             $page = $totalpages - 1;
1028         }
1029         $courseid = isset($course) ? $course->id : null;
1030         $first = true;
1031         $last = false;
1032         $i = $page * $perpage;
1034         $html  = html_writer::start_div('course-listing', array(
1035             'data-category' => 'search',
1036             'data-page' => $page,
1037             'data-totalpages' => $totalpages,
1038             'data-totalcourses' => $totalcourses
1039         ));
1040         $html .= html_writer::tag('h3', get_string('courses'));
1041         $html .= $this->search_pagination($totalcourses, $page, $perpage);
1042         $html .= html_writer::start_tag('ul', array('class' => 'ml'));
1043         foreach ($courses as $listitem) {
1044             $i++;
1045             if ($i == $totalcourses) {
1046                 $last = true;
1047             }
1048             $html .= $this->search_listitem($listitem, $courseid, $first, $last);
1049             $first = false;
1050         }
1051         $html .= html_writer::end_tag('ul');
1052         $html .= $this->search_pagination($totalcourses, $page, $perpage, true);
1053         $html .= html_writer::end_div();
1054         return $html;
1055     }
1057     /**
1058      * Displays pagination for search results.
1059      *
1060      * @param int $totalcourses The total number of courses to be displayed.
1061      * @param int $page The current page.
1062      * @param int $perpage The number of courses being displayed.
1063      * @param bool $showtotals Whether or not to print total information.
1064      * @return string
1065      */
1066     protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false) {
1067         $html = '';
1068         $totalpages = ceil($totalcourses / $perpage);
1069         if ($showtotals) {
1070             if ($totalpages == 1) {
1071                 $str = get_string('showingacourses', 'moodle', $totalcourses);
1072             } else {
1073                 $a = new stdClass;
1074                 $a->start = ($page * $perpage) + 1;
1075                 $a->end = min((($page + 1) * $perpage), $totalcourses);
1076                 $a->total = $totalcourses;
1077                 $str = get_string('showingxofycourses', 'moodle', $a);
1078             }
1079             $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
1080         }
1082         if ($totalcourses < $perpage) {
1083             return $html;
1084         }
1085         $aside = 2;
1086         $span = $aside * 2 + 1;
1087         $start = max($page - $aside, 0);
1088         $end = min($page + $aside, $totalpages - 1);
1089         if (($end - $start) < $span) {
1090             if ($start == 0) {
1091                 $end = min($totalpages - 1, $span - 1);
1092             } else if ($end == ($totalpages - 1)) {
1093                 $start = max(0, $end - $span + 1);
1094             }
1095         }
1096         $items = array();
1097         $baseurl = $this->page->url;
1098         if ($page > 0) {
1099             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
1100             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
1101             $items[] = '...';
1102         }
1103         for ($i = $start; $i <= $end; $i++) {
1104             $class = '';
1105             if ($page == $i) {
1106                 $class = 'active-page';
1107             }
1108             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
1109         }
1110         if ($page < ($totalpages - 1)) {
1111             $items[] = '...';
1112             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
1113             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
1114         }
1116         $html .= html_writer::div(join('', $items), 'listing-pagination');
1117         return $html;
1118     }
1120     /**
1121      * Renderers a search result course list item.
1122      *
1123      * This function will be called for every course being displayed by course_listing.
1124      *
1125      * @param course_in_list $course The course to produce HTML for.
1126      * @param int $selectedcourse The id of the currently selected course.
1127      * @return string
1128      */
1129     public function search_listitem(course_in_list $course, $selectedcourse) {
1131         $text = $course->get_formatted_name();
1132         $attributes = array(
1133             'class' => 'listitem listitem-course',
1134             'data-id' => $course->id,
1135             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
1136             'data-visible' => $course->visible ? '1' : '0'
1137         );
1139         $bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
1140         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
1141         $categoryname = coursecat::get($course->category)->get_formatted_name();
1143         $html  = html_writer::start_tag('li', $attributes);
1144         $html .= html_writer::start_div('clearfix');
1145         $html .= html_writer::start_div('float-left');
1146         $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
1147         $html .= html_writer::end_div();
1148         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
1149         $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left categoryname'));
1150         $html .= html_writer::start_div('float-right');
1151         $html .= $this->search_listitem_actions($course);
1152         $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
1153         $html .= html_writer::end_div();
1154         $html .= html_writer::end_div();
1155         $html .= html_writer::end_tag('li');
1156         return $html;
1157     }
1159     /**
1160      * Renderers actions for individual course actions.
1161      *
1162      * @param course_in_list $course The course to renderer actions for.
1163      * @return string
1164      */
1165     public function search_listitem_actions(course_in_list $course) {
1166         $baseurl = new moodle_url(
1167             '/course/managementsearch.php',
1168             array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
1169         );
1170         $actions = array();
1171         // Edit.
1172         if ($course->can_access()) {
1173             if ($course->can_edit()) {
1174                 $actions[] = $this->output->action_icon(
1175                     new moodle_url('/course/edit.php', array('id' => $course->id)),
1176                     new pix_icon('t/edit', get_string('edit')),
1177                     null,
1178                     array('class' => 'action-edit')
1179                 );
1180             }
1181             // Show/Hide.
1182             if ($course->can_change_visibility()) {
1183                 if ($course->visible) {
1184                     $actions[] = $this->output->action_icon(
1185                         new moodle_url($baseurl, array('action' => 'hidecourse')),
1186                         new pix_icon('t/show', get_string('hide')),
1187                         null,
1188                         array('data-action' => 'hide', 'class' => 'action-hide')
1189                     );
1190                 } else {
1191                     $actions[] = $this->output->action_icon(
1192                         new moodle_url($baseurl, array('action' => 'showcourse')),
1193                         new pix_icon('t/hide', get_string('show')),
1194                         null,
1195                         array('data-action' => 'show', 'class' => 'action-show')
1196                     );
1197                 }
1198             }
1199         }
1200         if (empty($actions)) {
1201             return '';
1202         }
1203         return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1204     }
1206     /**
1207      * Renders html to display a course search form
1208      *
1209      * @param string $value default value to populate the search field
1210      * @param string $format display format - 'plain' (default), 'short' or 'navbar'
1211      * @return string
1212      */
1213     public function course_search_form($value = '', $format = 'plain') {
1214         static $count = 0;
1215         $formid = 'coursesearch';
1216         if ((++$count) > 1) {
1217             $formid .= $count;
1218         }
1220         switch ($format) {
1221             case 'navbar' :
1222                 $formid = 'coursesearchnavbar';
1223                 $inputid = 'navsearchbox';
1224                 $inputsize = 20;
1225                 break;
1226             case 'short' :
1227                 $inputid = 'shortsearchbox';
1228                 $inputsize = 12;
1229                 break;
1230             default :
1231                 $inputid = 'coursesearchbox';
1232                 $inputsize = 30;
1233         }
1235         $strsearchcourses = get_string("searchcourses");
1236         $searchurl = new moodle_url('/course/management.php');
1238         $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get'));
1239         $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
1240         $output .= html_writer::tag('label', $strsearchcourses.': ', array('for' => $inputid));
1241         $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid,
1242             'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
1243         $output .= html_writer::empty_tag('input', array('type' => 'submit',
1244             'value' => get_string('go')));
1245         $output .= html_writer::end_tag('fieldset');
1246         $output .= html_writer::end_tag('form');
1248         return $output;
1249     }
1251     /**
1252      * Creates access hidden skip to links for the displayed sections.
1253      *
1254      * @param bool $displaycategorylisting
1255      * @param bool $displaycourselisting
1256      * @param bool $displaycoursedetail
1257      * @return string
1258      */
1259     public function accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail) {
1260         $html = html_writer::start_div('skiplinks accesshide');
1261         $url = new moodle_url($this->page->url);
1262         if ($displaycategorylisting) {
1263             $url->set_anchor('category-listing');
1264             $html .= html_writer::link($url, get_string('skiptocategorylisting'), array('class' => 'skip'));
1265         }
1266         if ($displaycourselisting) {
1267             $url->set_anchor('course-listing');
1268             $html .= html_writer::link($url, get_string('skiptocourselisting'), array('class' => 'skip'));
1269         }
1270         if ($displaycoursedetail) {
1271             $url->set_anchor('course-detail');
1272             $html .= html_writer::link($url, get_string('skiptocoursedetails'), array('class' => 'skip'));
1273         }
1274         $html .= html_writer::end_div();
1275         return $html;
1276     }