MDL-31830 core_course: new course category management interface
[moodle.git] / course / classes / management_renderer.php
CommitLineData
5dc361e1
SH
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/>.
16
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 */
24
25defined('MOODLE_INTERNAL') || die;
26
27require_once($CFG->dirroot.'/course/renderer.php');
28
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 */
36class core_course_management_renderer extends plugin_renderer_base {
37
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 }
51
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 }
81
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');
89
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 }
95
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 }
104
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) {
112
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);
121
122 $listing = \coursecat::get(0)->get_children();
123
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 }
147
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 }
187
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 }
218
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();
232
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()) {
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 }
288
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 }
314
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');
324
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 }
350
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) {
361
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 }
369
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;
385
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 }
411
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 }
439
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 }
473
474 $html .= html_writer::div(join('', $items), 'listing-pagination');
475 return $html;
476 }
477
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) {
492
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 );
500
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));
503
504 $html = html_writer::start_tag('li', $attributes);
505 $html .= html_writer::start_div('clearfix');
506
507 if ($category->can_resort()) {
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 }
511
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 }
524
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()) {
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 }
575
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()) {
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 }
640
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 }
662
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'];
672
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 }
682
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 }
698
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 }
716
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 }
738
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 }
761
762 /**
763 * Closes the grid.
764 *
765 * @return string
766 */
767 public function grid_end() {
768 return html_writer::end_div();
769 }
770
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) {
780
781 // Calculate Bootstrap grid sizing.
782 $bootstrapclass = 'span'.$size;
783
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 }
804
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 }
816
817 /**
818 * Closes a grid column.
819 *
820 * @return string
821 */
822 public function grid_column_end() {
823 return html_writer::end_div();
824 }
825
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;
846
847 if (empty($attributes['class'])) {
848 // Let devs override the class via $attributes.
849 $attributes['class'] = 'action-icon';
850 }
851
852 $icon = $this->render($pixicon);
853
854 if ($linktext) {
855 $text = $pixicon->attributes['alt'];
856 } else {
857 $text = '';
858 }
859
860 return $this->action_link($url, $icon.$text, $action, $attributes);
861 }
862
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 }
876
877 $menu = new action_menu;
878 $menu->attributes['class'] .= ' view-mode-selector vms';
879
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 }
899
900 $menu->actiontext = get_string('viewing', 'moodle', $selected);
901
902 $html = html_writer::start_div('view-mode-selector vms');
903 $html .= $this->render($menu);
904 $html .= html_writer::end_div();
905
906 return $html;
907 }
908
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;
930
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 }
953
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 }
978
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 }
1012
1013 $html .= html_writer::div(join('', $items), 'listing-pagination');
1014 return $html;
1015 }
1016
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) {
1027
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 );
1035
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();
1039
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 }
1055
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 }
1100
1101}