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