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