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