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