Merge branch 'MDL-40975-master' of git://github.com/andrewnicols/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('show', 'hide', 'expand', 'collapse', 'confirmcoursemove', 'move', 'cancel', 'confirm'),
48             'moodle'
49         );
50     }
52     /**
53      * Displays a heading for the management pages.
54      *
55      * @param string $heading The heading to display
56      * @param string|null $viewmode The current view mode if there are options.
57      * @param int|null $categoryid The currently selected category if there is one.
58      * @return string
59      */
60     public function management_heading($heading, $viewmode = null, $categoryid = null) {
61         $html = html_writer::start_div('coursecat-management-header clearfix');
62         if (!empty($heading)) {
63             $html .= $this->heading($heading);
64         }
65         if ($viewmode !== null) {
66             if ($viewmode === 'courses') {
67                 $categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
68                 $nothing = false;
69                 if ($categoryid === null) {
70                     $nothing = array('' => get_string('selectacategory'));
71                     $categoryid = '';
72                 }
73                 $select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
74                 $html .= $this->render($select);
75             }
76             $html .= $this->view_mode_selector(\core_course\management\helper::get_management_viewmodes(), $viewmode);
77         }
78         $html .= html_writer::end_div();
79         return $html;
80     }
82     /**
83      * Prepares the form element for the course category listing bulk actions.
84      *
85      * @return string
86      */
87     public function management_form_start() {
88         $form = array('action' => $this->page->url->out(), 'method' => 'POST', 'id' => 'coursecat-management');
90         $html = html_writer::start_tag('form', $form);
91         $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
92         $html .=  html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'bulkaction'));
93         return $html;
94     }
96     /**
97      * Closes the course category bulk management form.
98      *
99      * @return string
100      */
101     public function management_form_end() {
102         return html_writer::end_tag('form');
103     }
105     /**
106      * Presents a course category listing.
107      *
108      * @param coursecat $category The currently selected category. Also the category to highlight in the listing.
109      * @return string
110      */
111     public function category_listing(coursecat $category = null) {
113         if ($category === null) {
114             $selectedparents = array();
115             $selectedcategory = null;
116         } else {
117             $selectedparents = $category->get_parents();
118             $selectedparents[] = $category->id;
119             $selectedcategory = $category->id;
120         }
121         $catatlevel = \core_course\management\helper::get_expanded_categories('');
122         $catatlevel[] = array_shift($selectedparents);
123         $catatlevel = array_unique($catatlevel);
125         $listing = coursecat::get(0)->get_children();
127         $attributes = array(
128             'class' => 'ml',
129             'role' => 'tree',
130             'aria-labelledby' => 'category-listing-title'
131         );
133         $html  = html_writer::start_div('category-listing');
134         $html .= html_writer::tag('h3', get_string('categories'), array('id' => 'category-listing-title'));
135         $html .= $this->category_listing_actions($category);
136         $html .= html_writer::start_tag('ul', $attributes);
137         foreach ($listing as $listitem) {
138             // Render each category in the listing.
139             $subcategories = array();
140             if (in_array($listitem->id, $catatlevel)) {
141                 $subcategories = $listitem->get_children();
142             }
143             $html .= $this->category_listitem(
144                 $listitem,
145                 $subcategories,
146                 $listitem->get_children_count(),
147                 $selectedcategory,
148                 $selectedparents
149             );
150         }
151         $html .= html_writer::end_tag('ul');
152         $html .= $this->category_bulk_actions($category);
153         $html .= html_writer::end_div();
154         return $html;
155     }
157     /**
158      * Renders a category list item.
159      *
160      * This function gets called recursively to render sub categories.
161      *
162      * @param coursecat $category The category to render as listitem.
163      * @param coursecat[] $subcategories The subcategories belonging to the category being rented.
164      * @param int $totalsubcategories The total number of sub categories.
165      * @param int $selectedcategory The currently selected category
166      * @param int[] $selectedcategories The path to the selected category and its ID.
167      * @return string
168      */
169     public function category_listitem(coursecat $category, array $subcategories, $totalsubcategories,
170                                       $selectedcategory = null, $selectedcategories = array()) {
172         $isexpandable = ($totalsubcategories > 0);
173         $isexpanded = (!empty($subcategories));
174         $activecategory = ($selectedcategory === $category->id);
175         $attributes = array(
176             'class' => 'listitem listitem-category',
177             'data-id' => $category->id,
178             'data-expandable' => $isexpandable ? '1' : '0',
179             'data-expanded' => $isexpanded ? '1' : '0',
180             'data-selected' => $activecategory ? '1' : '0',
181             'data-visible' => $category->visible ? '1' : '0',
182             'role' => 'treeitem',
183             'aria-expanded' => $isexpanded ? 'true' : 'false'
184         );
185         $text = $category->get_formatted_name();
186         $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
187         $bcatinput = array('type' => 'checkbox', 'name' => 'bcat[]', 'value' => $category->id, 'class' => 'bulk-action-checkbox');
189         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
190             // Very very hardcoded here.
191             $bcatinput['style'] = 'visibility:hidden';
192         }
194         $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
195         if ($isexpanded) {
196             $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon'));
197             $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'collapse'));
198         } else if ($isexpandable) {
199             $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon'));
200             $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left', 'data-action' => 'expand'));
201         } else {
202             $icon = $this->output->pix_icon('i/navigationitem', '', 'moodle', array('class' => 'tree-icon'));
203             $icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left'));
204         }
205         $actions = \core_course\management\helper::get_category_listitem_actions($category);
206         $hasactions = !empty($actions) || $category->can_create_course();
208         $html = html_writer::start_tag('li', $attributes);
209         $html .= html_writer::start_div('clearfix');
210         $html .= html_writer::start_div('float-left ba-checkbox');
211         $html .= html_writer::empty_tag('input', $bcatinput).'&nbsp;';
212         $html .= html_writer::end_div();
213         $html .= $icon;
214         if ($hasactions) {
215             $html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname'));
216         } else {
217             $html .= html_writer::link($viewcaturl, $text, array('class' => 'float-left categoryname without-actions'));
218         }
219         $html .= html_writer::start_div('float-right');
220         if ($category->idnumber) {
221             $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
222         }
223         if ($hasactions) {
224             $html .= $this->category_listitem_actions($category, $actions);
225         }
226         $countid = 'course-count-'.$category->id;
227         $html .= html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid));
228         $html .= html_writer::span(
229             html_writer::span($category->get_courses_count()).$courseicon,
230             'course-count dimmed',
231             array('aria-labelledby' => $countid)
232         );
233         $html .= html_writer::end_div();
234         $html .= html_writer::end_div();
235         if ($isexpanded) {
236             $html .= html_writer::start_tag('ul', array('class' => 'ml', 'role' => 'group'));
237             $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
238             $catatlevel[] = array_shift($selectedcategories);
239             $catatlevel = array_unique($catatlevel);
240             foreach ($subcategories as $listitem) {
241                 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
242                 $html .= $this->category_listitem(
243                     $listitem,
244                     $childcategories,
245                     $listitem->get_children_count(),
246                     $selectedcategory,
247                     $selectedcategories
248                 );
249             }
250             $html .= html_writer::end_tag('ul');
251         }
252         $html .= html_writer::end_tag('li');
253         return $html;
254     }
256     /**
257      * Renderers the actions that are possible for the course category listing.
258      *
259      * These are not the actions associated with an individual category listing.
260      * That happens through category_listitem_actions.
261      *
262      * @param coursecat $category
263      * @return string
264      */
265     public function category_listing_actions(coursecat $category = null) {
266         $actions = array();
268         $cancreatecategory = $category && $category->can_create_subcategory();
269         $cancreatecategory = $cancreatecategory || coursecat::can_create_top_level_category();
270         if ($category === null) {
271             $category = coursecat::get(0);
272         }
274         if ($cancreatecategory) {
275             $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
276             $actions[] = html_writer::link($url, get_string('createnewcategory'));
277         }
278         if (coursecat::can_approve_course_requests()) {
279             $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
280         }
281         if (count($actions) === 0) {
282             return '';
283         }
284         return html_writer::div(join(' | ', $actions), 'listing-actions category-listing-actions');
285     }
287     /**
288      * Renderers the actions for individual category list items.
289      *
290      * @param coursecat $category
291      * @param array $actions
292      * @return string
293      */
294     public function category_listitem_actions(coursecat $category, array $actions = null) {
295         if ($actions === null) {
296             $actions = \core_course\management\helper::get_category_listitem_actions($category);
297         }
298         $menu = new action_menu();
299         $menu->attributes['class'] .= ' category-item-actions item-actions';
300         $hasitems = false;
301         foreach ($actions as $key => $action) {
302             $hasitems = true;
303             $menu->add(new action_menu_link(
304                 $action['url'],
305                 $action['icon'],
306                 $action['string'],
307                 in_array($key, array('show', 'hide', 'moveup', 'movedown')),
308                 array('data-action' => $key, 'class' => 'action-'.$key)
309             ));
310         }
311         if (!$hasitems) {
312             return '';
313         }
314         return $this->render($menu);
315     }
317     /**
318      * Renders bulk actions for categories.
319      *
320      * @param coursecat $category The currently selected category if there is one.
321      * @return string
322      */
323     public function category_bulk_actions(coursecat $category = null) {
324         // Resort courses.
325         // Change parent.
326         $strgo = new lang_string('go');
328         $html  = html_writer::start_div('category-bulk-actions bulk-actions');
329         if (coursecat::can_resort_any()) {
330             $selectoptions = array(
331                 'selectedcategories' => get_string('selectedcategories'),
332                 'allcategories' => get_string('allcategories')
333             );
334             $form = html_writer::start_div();
335             if ($category) {
336                 $selectoptions = array('thiscategory' => get_string('thiscategory')) + $selectoptions;
337                 $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentcategoryid', 'value' => $category->id));
338             }
339             $form .= html_writer::div(
340                 html_writer::select(
341                     $selectoptions,
342                     'selectsortby',
343                     'selectedcategories',
344                     false
345                 )
346             );
347             $form .= html_writer::div(
348                 html_writer::select(
349                     array(
350                         'name' => get_string('sortcategoriesbyname'),
351                         'idnumber' => get_string('sortcategoriesbyidnumber'),
352                         'none' => get_string('dontsortcategories')
353                     ),
354                     'resortcategoriesby',
355                     'name',
356                     false
357                 )
358             );
359             $form .= html_writer::div(
360                 html_writer::select(
361                     array(
362                         'fullname' => get_string('sortcoursesbyfullname'),
363                         'shortname' => get_string('sortcoursesbyshortname'),
364                         'idnumber' => get_string('sortcoursesbyidnumber'),
365                         'none' => get_string('dontsortcourses')
366                     ),
367                     'resortcoursesby',
368                     'fullname',
369                     false
370                 )
371             );
372             $form .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'bulksort', 'value' => get_string('sort')));
373             $form .= html_writer::end_div();
374             $html .= $this->detail_pair(
375                 get_string('sorting'),
376                 $form
377             );
378         }
379         if (coursecat::can_change_parent_any()) {
380             $options = array();
381             if (has_capability('moodle/category:manage', context_system::instance())) {
382                 $options[0] = coursecat::get(0)->get_formatted_name();
383             }
384             $options += coursecat::make_categories_list('moodle/category:manage');
385             $select = html_writer::select(
386                 $options,
387                 'movecategoriesto',
388                 '',
389                 array('' => 'choosedots'),
390                 array('aria-labelledby' => 'moveselectedcategoriesto')
391             );
392             $submit = array('type' => 'submit', 'name' => 'bulkmovecategories', 'value' => get_string('move'));
393             $html .= $this->detail_pair(
394                 html_writer::span(get_string('moveselectedcategoriesto'), '', array('id' => 'moveselectedcategoriesto')),
395                 $select . html_writer::empty_tag('input', $submit)
396             );
397         }
398         $html .= html_writer::end_div();
399         return $html;
400     }
402     /**
403      * Renders a course listing.
404      *
405      * @param coursecat $category The currently selected category. This is what the listing is focused on.
406      * @param course_in_list $course The currently selected course.
407      * @param int $page The page being displayed.
408      * @param int $perpage The number of courses to display per page.
409      * @return string
410      */
411     public function course_listing(coursecat $category = null, course_in_list $course = null, $page = 0, $perpage = 20) {
413         if ($category === null) {
414             $html = html_writer::start_div('select-a-category');
415             $html .= html_writer::tag('h3', get_string('courses'));
416             $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
417             $html .= html_writer::end_div();
418             return $html;
419         }
421         $page = max($page, 0);
422         $perpage = max($perpage, 2);
423         $totalcourses = $category->coursecount;
424         $totalpages = ceil($totalcourses / $perpage);
425         if ($page > $totalpages - 1) {
426             $page = $totalpages - 1;
427         }
428         $options = array(
429             'offset' => $page * $perpage,
430             'limit' => $perpage
431         );
432         $courseid = isset($course) ? $course->id : null;
433         $class = '';
434         if ($page === 0) {
435             $class .= ' firstpage';
436         }
437         if ($page + 1 === (int)$totalpages) {
438             $class .= ' lastpage';
439         }
441         $html  = html_writer::start_div('course-listing'.$class, array(
442             'data-category' => $category->id,
443             'data-page' => $page,
444             'data-totalpages' => $totalpages,
445             'data-totalcourses' => $totalcourses,
446             'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
447         ));
448         $html .= html_writer::tag('h3', $category->get_formatted_name());
449         $html .= $this->course_listing_actions($category, $course, $perpage);
450         $html .= $this->listing_pagination($category, $page, $perpage);
451         $html .= html_writer::start_tag('ul', array('class' => 'ml'));
452         foreach ($category->get_courses($options) as $listitem) {
453             $html .= $this->course_listitem($category, $listitem, $courseid);
454         }
455         $html .= html_writer::end_tag('ul');
456         $html .= $this->listing_pagination($category, $page, $perpage, true);
457         $html .= $this->course_bulk_actions($category);
458         $html .= html_writer::end_div();
459         return $html;
460     }
462     /**
463      * Renders pagination for a course listing.
464      *
465      * @param coursecat $category The category to produce pagination for.
466      * @param int $page The current page.
467      * @param int $perpage The number of courses to display per page.
468      * @param bool $showtotals Set to true to show the total number of courses and what is being displayed.
469      * @return string
470      */
471     protected function listing_pagination(coursecat $category, $page, $perpage, $showtotals = false) {
472         $html = '';
473         $totalcourses = $category->get_courses_count();
474         $totalpages = ceil($totalcourses / $perpage);
475         if ($showtotals) {
476             if ($totalpages == 0) {
477                 $str = get_string('nocoursesyet');
478             } else if ($totalpages == 1) {
479                 $str = get_string('showingacourses', 'moodle', $totalcourses);
480             } else {
481                 $a = new stdClass;
482                 $a->start = ($page * $perpage) + 1;
483                 $a->end = min((($page + 1) * $perpage), $totalcourses);
484                 $a->total = $totalcourses;
485                 $str = get_string('showingxofycourses', 'moodle', $a);
486             }
487             $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
488         }
490         if ($totalcourses <= $perpage) {
491             return $html;
492         }
493         $aside = 2;
494         $span = $aside * 2 + 1;
495         $start = max($page - $aside, 0);
496         $end = min($page + $aside, $totalpages - 1);
497         if (($end - $start) < $span) {
498             if ($start == 0) {
499                 $end = min($totalpages - 1, $span - 1);
500             } else if ($end == ($totalpages - 1)) {
501                 $start = max(0, $end - $span + 1);
502             }
503         }
504         $items = array();
505         $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
506         if ($page > 0) {
507             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
508             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
509             $items[] = '...';
510         }
511         for ($i = $start; $i <= $end; $i++) {
512             $class = '';
513             if ($page == $i) {
514                 $class = 'active-page';
515             }
516             $pageurl = new moodle_url($baseurl, array('page' => $i));
517             $items[] = $this->action_button($pageurl, $i + 1, null, $class, get_string('pagea', 'moodle', $i+1));
518         }
519         if ($page < ($totalpages - 1)) {
520             $items[] = '...';
521             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
522             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
523         }
525         $html .= html_writer::div(join('', $items), 'listing-pagination');
526         return $html;
527     }
529     /**
530      * Renderers a course list item.
531      *
532      * This function will be called for every course being displayed by course_listing.
533      *
534      * @param coursecat $category The currently selected category and the category the course belongs to.
535      * @param course_in_list $course The course to produce HTML for.
536      * @param int $selectedcourse The id of the currently selected course.
537      * @return string
538      */
539     public function course_listitem(coursecat $category, course_in_list $course, $selectedcourse) {
541         $text = $course->get_formatted_name();
542         $attributes = array(
543             'class' => 'listitem listitem-course',
544             'data-id' => $course->id,
545             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
546             'data-visible' => $course->visible ? '1' : '0'
547         );
549         $bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
550         if (!$category->has_manage_capability()) {
551             // Very very hardcoded here.
552             $bulkcourseinput['style'] = 'visibility:hidden';
553         }
555         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
557         $html  = html_writer::start_tag('li', $attributes);
558         $html .= html_writer::start_div('clearfix');
560         if ($category->can_resort_courses()) {
561             // In order for dnd to be available the user must be able to resort the category children..
562             $html .= html_writer::div($this->output->pix_icon('i/dragdrop', get_string('dndcourse')), 'float-left drag-handle');
563         }
565         $html .= html_writer::start_div('ba-checkbox float-left');
566         $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
567         $html .= html_writer::end_div();
568         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
569         $html .= html_writer::start_div('float-right');
570         if ($course->idnumber) {
571             $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
572         }
573         $html .= $this->course_listitem_actions($category, $course);
574         $html .= html_writer::end_div();
575         $html .= html_writer::end_div();
576         $html .= html_writer::end_tag('li');
577         return $html;
578     }
580     /**
581      * Renderers actions for the course listing.
582      *
583      * Not to be confused with course_listitem_actions which renderers the actions for individual courses.
584      *
585      * @param coursecat $category
586      * @param course_in_list $course The currently selected course.
587      * @param int $perpage
588      * @return string
589      */
590     public function course_listing_actions(coursecat $category, course_in_list $course = null, $perpage = 20) {
591         $actions = array();
592         if ($category->can_create_course()) {
593             $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
594             $actions[] = html_writer::link($url, get_string('createnewcourse'));
595         }
596         if ($category->can_request_course()) {
597             // Request a new course.
598             $url = new moodle_url('/course/request.php', array('return' => 'management'));
599             $actions[] = html_writer::link($url, get_string('requestcourse'));
600         }
601         if ($category->can_resort_courses()) {
602             $params = $this->page->url->params();
603             $params['action'] = 'resortcourses';
604             $params['sesskey'] = sesskey();
605             $baseurl = new moodle_url('/course/management.php', $params);
606             $fullnameurl = new moodle_url($baseurl, array('resort' => 'fullname'));
607             $shortnameurl = new moodle_url($baseurl, array('resort' => 'shortname'));
608             $idnumberurl = new moodle_url($baseurl, array('resort' => 'idnumber'));
609             $menu = new action_menu(array(
610                 new action_menu_link_secondary($fullnameurl, null, get_string('resortbyfullname')),
611                 new action_menu_link_secondary($shortnameurl, null, get_string('resortbyshortname')),
612                 new action_menu_link_secondary($idnumberurl, null, get_string('resortbyidnumber'))
613             ));
614             $menu->set_menu_trigger(get_string('resortcourses'));
615             $actions[] = $this->render($menu);
616         }
617         $strall = get_string('all');
618         $menu = new action_menu(array(
619             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
620             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
621             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
622             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
623             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
624             new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
625         ));
626         if ((int)$perpage === 999) {
627             $perpage = $strall;
628         }
629         $menu->attributes['class'] .= ' courses-per-page';
630         $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
631         $actions[] = $this->render($menu);
632         return html_writer::div(join(' | ', $actions), 'listing-actions course-listing-actions');
633     }
635     /**
636      * Renderers actions for individual course actions.
637      *
638      * @param coursecat $category The currently selected category.
639      * @param course_in_list $course The course to renderer actions for.
640      * @return string
641      */
642     public function course_listitem_actions(coursecat $category, course_in_list $course) {
643         $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
644         if (empty($actions)) {
645             return '';
646         }
647         $actionshtml = array();
648         foreach ($actions as $action) {
649             $action['attributes']['role'] = 'button';
650             $actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
651         }
652         return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
653     }
655     /**
656      * Renderers bulk actions that can be performed on courses.
657      *
658      * @param coursecat $category The currently selected category and the category in which courses that
659      *      are selectable belong.
660      * @return string
661      */
662     public function course_bulk_actions(coursecat $category) {
663         $html  = html_writer::start_div('course-bulk-actions bulk-actions');
664         if ($category->can_move_courses_out_of()) {
665             $options = coursecat::make_categories_list('moodle/category:manage');
666             $select = html_writer::select(
667                 $options,
668                 'movecoursesto',
669                 '',
670                 array('' => 'choosedots'),
671                 array('aria-labelledby' => 'moveselectedcoursesto')
672             );
673             $submit = array('type' => 'submit', 'name' => 'bulkmovecourses', 'value' => get_string('move'));
674             $html .= $this->detail_pair(
675                 html_writer::span(get_string('moveselectedcoursesto'), '', array('id' => 'moveselectedcoursesto')),
676                 $select . html_writer::empty_tag('input', $submit)
677             );
678         }
679         $html .= html_writer::end_div();
680         return $html;
681     }
683     /**
684      * Renderers detailed course information.
685      *
686      * @param course_in_list $course The course to display details for.
687      * @return string
688      */
689     public function course_detail(course_in_list $course) {
690         $details = \core_course\management\helper::get_course_detail_array($course);
691         $fullname = $details['fullname']['value'];
693         $html  = html_writer::start_div('course-detail');
694         $html .= html_writer::tag('h3', $fullname);
695         $html .= $this->course_detail_actions($course);
696         foreach ($details as $class => $data) {
697             $html .= $this->detail_pair($data['key'], $data['value'], $class);
698         }
699         $html .= html_writer::end_div();
700         return $html;
701     }
703     /**
704      * Renderers a key value pair of information for display.
705      *
706      * @param string $key
707      * @param string $value
708      * @param string $class
709      * @return string
710      */
711     protected function detail_pair($key, $value, $class ='') {
712         $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
713         $html .= html_writer::div(html_writer::span($key), 'pair-key span3 yui3-u-1-4');
714         $html .= html_writer::div(html_writer::span($value), 'pair-value span9 yui3-u-3-4');
715         $html .= html_writer::end_div();
716         return $html;
717     }
719     /**
720      * A collection of actions for a course.
721      *
722      * @param course_in_list $course The course to display actions for.
723      * @return string
724      */
725     public function course_detail_actions(course_in_list $course) {
726         $actions = \core_course\management\helper::get_course_detail_actions($course);
727         if (empty($actions)) {
728             return '';
729         }
730         $options = array();
731         foreach ($actions as $action) {
732             $options[] = $this->action_link($action['url'], $action['string']);
733         }
734         return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
735     }
737     /**
738      * Creates an action button (styled link)
739      *
740      * @param moodle_url $url The URL to go to when clicked.
741      * @param string $text The text for the button.
742      * @param string $id An id to give the button.
743      * @param string $class A class to give the button.
744      * @return string
745      */
746     protected function action_button(moodle_url $url, $text, $id = null, $class = null, $title = null) {
747         $attributes = array(
748             'class' => 'yui3-button',
749         );
750         if (!is_null($id)) {
751             $attributes['id'] = $id;
752         }
753         if (!is_null($class)) {
754             $attributes['class'] .= ' '.$class;
755         }
756         if (is_null($title)) {
757             $title = $text;
758         }
759         $attributes['title'] = $title;
760         return html_writer::link($url, $text, $attributes);
761     }
763     /**
764      * Opens a grid.
765      *
766      * Call {@link core_course_management_renderer::grid_column_start()} to create columns.
767      *
768      * @param string $id An id to give this grid.
769      * @param string $class A class to give this grid.
770      * @return string
771      */
772     public function grid_start($id = null, $class = null) {
773         $gridclass = 'grid-row-r row-fluid';
774         if (is_null($class)) {
775             $class = $gridclass;
776         } else {
777             $class .= ' ' . $gridclass;
778         }
779         $attributes = array();
780         if (!is_null($id)) {
781             $attributes['id'] = $id;
782         }
783         return html_writer::start_div($class, $attributes);
784     }
786     /**
787      * Closes the grid.
788      *
789      * @return string
790      */
791     public function grid_end() {
792         return html_writer::end_div();
793     }
795     /**
796      * Opens a grid column
797      *
798      * @param int $size The number of segments this column should span.
799      * @param string $id An id to give the column.
800      * @param string $class A class to give the column.
801      * @return string
802      */
803     public function grid_column_start($size, $id = null, $class = null) {
805         // Calculate Bootstrap grid sizing.
806         $bootstrapclass = 'span'.$size;
808         // Calculate YUI grid sizing.
809         if ($size === 12) {
810             $maxsize = 1;
811             $size = 1;
812         } else {
813             $maxsize = 12;
814             $divisors = array(8, 6, 5, 4, 3, 2);
815             foreach ($divisors as $divisor) {
816                 if (($maxsize % $divisor === 0) && ($size % $divisor === 0)) {
817                     $maxsize = $maxsize / $divisor;
818                     $size = $size / $divisor;
819                     break;
820                 }
821             }
822         }
823         if ($maxsize > 1) {
824             $yuigridclass =  "grid-col-{$size}-{$maxsize} grid-col";
825         } else {
826             $yuigridclass =  "grid-col-1 grid-col";
827         }
829         if (is_null($class)) {
830             $class = $yuigridclass . ' ' . $bootstrapclass;
831         } else {
832             $class .= ' ' . $yuigridclass . ' ' . $bootstrapclass;
833         }
834         $attributes = array();
835         if (!is_null($id)) {
836             $attributes['id'] = $id;
837         }
838         return html_writer::start_div($class, $attributes);
839     }
841     /**
842      * Closes a grid column.
843      *
844      * @return string
845      */
846     public function grid_column_end() {
847         return html_writer::end_div();
848     }
850     /**
851      * Renders an action_icon.
852      *
853      * This function uses the {@link core_renderer::action_link()} method for the
854      * most part. What it does different is prepare the icon as HTML and use it
855      * as the link text.
856      *
857      * @param string|moodle_url $url A string URL or moodel_url
858      * @param pix_icon $pixicon
859      * @param component_action $action
860      * @param array $attributes associative array of html link attributes + disabled
861      * @param bool $linktext show title next to image in link
862      * @return string HTML fragment
863      */
864     public function action_icon($url, pix_icon $pixicon, component_action $action = null,
865                                 array $attributes = null, $linktext = false) {
866         if (!($url instanceof moodle_url)) {
867             $url = new moodle_url($url);
868         }
869         $attributes = (array)$attributes;
871         if (empty($attributes['class'])) {
872             // Let devs override the class via $attributes.
873             $attributes['class'] = 'action-icon';
874         }
876         $icon = $this->render($pixicon);
878         if ($linktext) {
879             $text = $pixicon->attributes['alt'];
880         } else {
881             $text = '';
882         }
884         return $this->action_link($url, $icon.$text, $action, $attributes);
885     }
887     /**
888      * Displays a view mode selector.
889      *
890      * @param array $modes An array of view modes.
891      * @param string $currentmode The current view mode.
892      * @param moodle_url $url The URL to use when changing actions. Defaults to the page URL.
893      * @param string $param The param name.
894      * @return string
895      */
896     public function view_mode_selector(array $modes, $currentmode, moodle_url $url = null, $param = 'view') {
897         if ($url === null) {
898             $url = $this->page->url;
899         }
901         $menu = new action_menu;
902         $menu->attributes['class'] .= ' view-mode-selector vms';
904         $selected = null;
905         foreach ($modes as $mode => $modestr) {
906             $attributes = array(
907                 'class' => 'vms-mode',
908                 'data-mode' => $mode
909             );
910             if ($currentmode === $mode) {
911                 $attributes['class'] .= ' currentmode';
912                 $selected = $modestr;
913             }
914             if ($selected === null) {
915                 $selected = $modestr;
916             }
917             $modeurl = new moodle_url($url, array($param => $mode));
918             if ($mode === 'default') {
919                 $modeurl->remove_params($param);
920             }
921             $menu->add(new action_menu_link_secondary($modeurl, null, $modestr, $attributes));
922         }
924         $menu->set_menu_trigger(get_string('viewing', 'moodle', $selected));
926         $html = html_writer::start_div('view-mode-selector vms');
927         $html .= $this->render($menu);
928         $html .= html_writer::end_div();
930         return $html;
931     }
933     /**
934      * Displays a search result listing.
935      *
936      * @param array $courses The courses to display.
937      * @param int $totalcourses The total number of courses to display.
938      * @param course_in_list $course The currently selected course if there is one.
939      * @param int $page The current page, starting at 0.
940      * @param int $perpage The number of courses to display per page.
941      * @return string
942      */
943     public function search_listing(array $courses, $totalcourses, course_in_list $course = null, $page = 0, $perpage = 20) {
944         $page = max($page, 0);
945         $perpage = max($perpage, 2);
946         $totalpages = ceil($totalcourses / $perpage);
947         if ($page > $totalpages - 1) {
948             $page = $totalpages - 1;
949         }
950         $courseid = isset($course) ? $course->id : null;
951         $first = true;
952         $last = false;
953         $i = $page * $perpage;
955         $html  = html_writer::start_div('course-listing', array(
956             'data-category' => 'search',
957             'data-page' => $page,
958             'data-totalpages' => $totalpages,
959             'data-totalcourses' => $totalcourses
960         ));
961         $html .= html_writer::tag('h3', get_string('courses'));
962         $html .= $this->search_pagination($totalcourses, $page, $perpage);
963         $html .= html_writer::start_tag('ul', array('class' => 'ml'));
964         foreach ($courses as $listitem) {
965             $i++;
966             if ($i == $totalcourses) {
967                 $last = true;
968             }
969             $html .= $this->search_listitem($listitem, $courseid, $first, $last);
970             $first = false;
971         }
972         $html .= html_writer::end_tag('ul');
973         $html .= $this->search_pagination($totalcourses, $page, $perpage, true);
974         $html .= html_writer::end_div();
975         return $html;
976     }
978     /**
979      * Displays pagination for search results.
980      *
981      * @param int $totalcourses The total number of courses to be displayed.
982      * @param int $page The current page.
983      * @param int $perpage The number of courses being displayed.
984      * @param bool $showtotals Whether or not to print total information.
985      * @return string
986      */
987     protected function search_pagination($totalcourses, $page, $perpage, $showtotals = false) {
988         $html = '';
989         $totalpages = ceil($totalcourses / $perpage);
990         if ($showtotals) {
991             if ($totalpages == 1) {
992                 $str = get_string('showingacourses', 'moodle', $totalcourses);
993             } else {
994                 $a = new stdClass;
995                 $a->start = ($page * $perpage) + 1;
996                 $a->end = min((($page + 1) * $perpage), $totalcourses);
997                 $a->total = $totalcourses;
998                 $str = get_string('showingxofycourses', 'moodle', $a);
999             }
1000             $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
1001         }
1003         if ($totalcourses < $perpage) {
1004             return $html;
1005         }
1006         $aside = 2;
1007         $span = $aside * 2 + 1;
1008         $start = max($page - $aside, 0);
1009         $end = min($page + $aside, $totalpages - 1);
1010         if (($end - $start) < $span) {
1011             if ($start == 0) {
1012                 $end = min($totalpages - 1, $span - 1);
1013             } else if ($end == ($totalpages - 1)) {
1014                 $start = max(0, $end - $span + 1);
1015             }
1016         }
1017         $items = array();
1018         $baseurl = $this->page->url;
1019         if ($page > 0) {
1020             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
1021             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
1022             $items[] = '...';
1023         }
1024         for ($i = $start; $i <= $end; $i++) {
1025             $class = '';
1026             if ($page == $i) {
1027                 $class = 'active-page';
1028             }
1029             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $i)), $i + 1, null, $class);
1030         }
1031         if ($page < ($totalpages - 1)) {
1032             $items[] = '...';
1033             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
1034             $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
1035         }
1037         $html .= html_writer::div(join('', $items), 'listing-pagination');
1038         return $html;
1039     }
1041     /**
1042      * Renderers a search result course list item.
1043      *
1044      * This function will be called for every course being displayed by course_listing.
1045      *
1046      * @param course_in_list $course The course to produce HTML for.
1047      * @param int $selectedcourse The id of the currently selected course.
1048      * @return string
1049      */
1050     public function search_listitem(course_in_list $course, $selectedcourse) {
1052         $text = $course->get_formatted_name();
1053         $attributes = array(
1054             'class' => 'listitem listitem-course',
1055             'data-id' => $course->id,
1056             'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
1057             'data-visible' => $course->visible ? '1' : '0'
1058         );
1060         $bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
1061         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
1062         $categoryname = coursecat::get($course->category)->get_formatted_name();
1064         $html  = html_writer::start_tag('li', $attributes);
1065         $html .= html_writer::start_div('clearfix');
1066         $html .= html_writer::start_div('float-left');
1067         $html .= html_writer::empty_tag('input', $bulkcourseinput).'&nbsp;';
1068         $html .= html_writer::end_div();
1069         $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
1070         $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left categoryname'));
1071         $html .= html_writer::start_div('float-right');
1072         $html .= $this->search_listitem_actions($course);
1073         $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
1074         $html .= html_writer::end_div();
1075         $html .= html_writer::end_div();
1076         $html .= html_writer::end_tag('li');
1077         return $html;
1078     }
1080     /**
1081      * Renderers actions for individual course actions.
1082      *
1083      * @param course_in_list $course The course to renderer actions for.
1084      * @return string
1085      */
1086     public function search_listitem_actions(course_in_list $course) {
1087         $baseurl = new moodle_url(
1088             '/course/managementsearch.php',
1089             array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
1090         );
1091         $actions = array();
1092         // Edit.
1093         if ($course->can_access()) {
1094             if ($course->can_edit()) {
1095                 $actions[] = $this->output->action_icon(
1096                     new moodle_url('/course/edit.php', array('id' => $course->id)),
1097                     new pix_icon('t/edit', get_string('edit')),
1098                     null,
1099                     array('class' => 'action-edit')
1100                 );
1101             }
1102             // Show/Hide.
1103             if ($course->can_change_visibility()) {
1104                 if ($course->visible) {
1105                     $actions[] = $this->output->action_icon(
1106                         new moodle_url($baseurl, array('action' => 'hidecourse')),
1107                         new pix_icon('t/show', get_string('hide')),
1108                         null,
1109                         array('data-action' => 'hide', 'class' => 'action-hide')
1110                     );
1111                 } else {
1112                     $actions[] = $this->output->action_icon(
1113                         new moodle_url($baseurl, array('action' => 'showcourse')),
1114                         new pix_icon('t/hide', get_string('show')),
1115                         null,
1116                         array('data-action' => 'show', 'class' => 'action-show')
1117                     );
1118                 }
1119             }
1120         }
1121         if (empty($actions)) {
1122             return '';
1123         }
1124         return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1125     }