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