MDL-31830 course: management behat tests corrected after fixes
[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() {
484c4c6c 45 $this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
5dc361e1
SH
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') {
484c4c6c 67 $categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
5dc361e1
SH
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'));
b488058f 127 $html .= $this->category_listing_actions($category);
5dc361e1
SH
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(),
f454e324 139 $selectedcategory,
5dc361e1
SH
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 }
148
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));
f454e324 165 $activecategory = ($selectedcategory === $category->id);
5dc361e1
SH
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');
b488058f
SH
177
178 if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
179 // Very very hardcoded here.
180 $bcatinput['style'] = 'visibility:hidden';
181 }
182
5dc361e1
SH
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 }
b488058f 194 $actions = \core_course\management\helper::get_category_listitem_actions($category);
484c4c6c 195 $hasactions = !empty($actions) || $category->can_create_course();
5dc361e1
SH
196
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;
b488058f
SH
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 }
5dc361e1 208 $html .= html_writer::start_div('float-right');
b488058f
SH
209 if ($hasactions) {
210 $html .= $this->category_listitem_actions($category, $actions);
211 }
5dc361e1
SH
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 }
233
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 */
b488058f 243 public function category_listing_actions(coursecat $category = null) {
5dc361e1
SH
244 $actions = array();
245 $createtoplevel = coursecat::can_create_top_level_category();
b488058f
SH
246 $createsubcategory = $category && $category->can_create_subcategory();
247 if ($category === null) {
248 $category = coursecat::get(0);
249 }
5dc361e1
SH
250
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 }
484c4c6c
SH
279 if (coursecat::can_approve_course_requests()) {
280 $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
281 }
67e1f268 282 if ($category->can_resort_subcategories()) {
5dc361e1
SH
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 ));
b488058f
SH
300 if ($category->id === 0) {
301 $menu->actiontext = get_string('resortcategories');
302 } else {
303 $menu->actiontext = get_string('resortsubcategories');
304 }
5dc361e1
SH
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 }
313
314 /**
315 * Renderers the actions for individual category list items.
316 *
317 * @param coursecat $category
318 * @return string
319 */
b488058f
SH
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 }
5dc361e1
SH
324 $menu = new action_menu();
325 $menu->attributes['class'] .= ' category-item-actions item-actions';
326 $hasitems = false;
b488058f 327 foreach ($actions as $key => $action) {
5dc361e1
SH
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 }
342
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');
352
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 }
378
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) {
389
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 }
397
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;
484c4c6c
SH
410 $class = '';
411 if ($page === 0) {
412 $class .= ' firstpage';
413 }
414 if ($page + 1 === (int)$totalpages) {
415 $class .= ' lastpage';
416 }
5dc361e1 417
484c4c6c 418 $html = html_writer::start_div('course-listing'.$class, array(
5dc361e1
SH
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) {
f454e324 430 $html .= $this->course_listitem($category, $listitem, $courseid);
5dc361e1
SH
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 }
438
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 }
466
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 }
500
501 $html .= html_writer::div(join('', $items), 'listing-pagination');
502 return $html;
503 }
504
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.
5dc361e1
SH
513 * @return string
514 */
f454e324 515 public function course_listitem(coursecat $category, course_in_list $course, $selectedcourse) {
5dc361e1
SH
516
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 );
524
525 $bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
484c4c6c
SH
526 if (!$category->has_manage_capability()) {
527 // Very very hardcoded here.
528 $bulkcourseinput['style'] = 'visibility:hidden';
529 }
530
5dc361e1
SH
531 $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
532
533 $html = html_writer::start_tag('li', $attributes);
534 $html .= html_writer::start_div('clearfix');
535
67e1f268 536 if ($category->can_resort_courses()) {
5dc361e1
SH
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 }
540
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');
5dc361e1 546 $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'dimmed idnumber'));
f454e324 547 $html .= $this->course_listitem_actions($category, $course);
5dc361e1
SH
548 $html .= html_writer::end_div();
549 $html .= html_writer::end_div();
550 $html .= html_writer::end_tag('li');
551 return $html;
552 }
553
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 }
484c4c6c
SH
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 }
67e1f268 575 if ($category->can_resort_courses()) {
5dc361e1
SH
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 }
609
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.
5dc361e1
SH
615 * @return string
616 */
f454e324 617 public function course_listitem_actions(coursecat $category, course_in_list $course) {
484c4c6c 618 $actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
5dc361e1
SH
619 if (empty($actions)) {
620 return '';
621 }
484c4c6c
SH
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');
5dc361e1
SH
627 }
628
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 }
650
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'];
660
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 }
670
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 ='') {
484c4c6c
SH
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');
5dc361e1
SH
683 $html .= html_writer::end_div();
684 return $html;
685 }
686
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) {
484c4c6c 700 $options[] = $this->action_link($action['url'], $action['string']);
5dc361e1 701 }
484c4c6c 702 return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
5dc361e1
SH
703 }
704
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 }
726
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 }
749
750 /**
751 * Closes the grid.
752 *
753 * @return string
754 */
755 public function grid_end() {
756 return html_writer::end_div();
757 }
758
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) {
768
769 // Calculate Bootstrap grid sizing.
770 $bootstrapclass = 'span'.$size;
771
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 }
792
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 }
804
805 /**
806 * Closes a grid column.
807 *
808 * @return string
809 */
810 public function grid_column_end() {
811 return html_writer::end_div();
812 }
813
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;
834
835 if (empty($attributes['class'])) {
836 // Let devs override the class via $attributes.
837 $attributes['class'] = 'action-icon';
838 }
839
840 $icon = $this->render($pixicon);
841
842 if ($linktext) {
843 $text = $pixicon->attributes['alt'];
844 } else {
845 $text = '';
846 }
847
848 return $this->action_link($url, $icon.$text, $action, $attributes);
849 }
850
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 }
864
865 $menu = new action_menu;
866 $menu->attributes['class'] .= ' view-mode-selector vms';
867
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 }
887
888 $menu->actiontext = get_string('viewing', 'moodle', $selected);
889
890 $html = html_writer::start_div('view-mode-selector vms');
891 $html .= $this->render($menu);
892 $html .= html_writer::end_div();
893
894 return $html;
895 }
896
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;
918
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 }
941
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 }
966
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 }
1000
1001 $html .= html_writer::div(join('', $items), 'listing-pagination');
1002 return $html;
1003 }
1004
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) {
1015
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 );
1023
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();
1027
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 }
1043
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.
484c4c6c
SH
1057 if ($course->can_access()) {
1058 if ($course->can_edit()) {
5dc361e1 1059 $actions[] = $this->output->action_icon(
484c4c6c
SH
1060 new moodle_url('/course/edit.php', array('id' => $course->id)),
1061 new pix_icon('t/edit', get_string('edit')),
5dc361e1 1062 null,
484c4c6c 1063 array('class' => 'action-edit')
5dc361e1
SH
1064 );
1065 }
484c4c6c
SH
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 }
5dc361e1
SH
1084 }
1085 if (empty($actions)) {
1086 return '';
1087 }
1088 return html_writer::span(join('', $actions), 'course-item-actions item-actions');
1089 }
1090
1091}