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