MDL-67264 core_course: Activity chooser new feature
[moodle.git] / course / management.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 * Course and category management interfaces.
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
25require_once('../config.php');
5dc361e1
SH
26require_once($CFG->dirroot.'/course/lib.php');
27
28$categoryid = optional_param('categoryid', null, PARAM_INT);
cda49969 29$selectedcategoryid = optional_param('selectedcategoryid', null, PARAM_INT);
5dc361e1
SH
30$courseid = optional_param('courseid', null, PARAM_INT);
31$action = optional_param('action', false, PARAM_ALPHA);
32$page = optional_param('page', 0, PARAM_INT);
33$perpage = optional_param('perpage', null, PARAM_INT);
34$viewmode = optional_param('view', 'default', PARAM_ALPHA); // Can be one of default, combined, courses, or categories.
35
36// Search related params.
37$search = optional_param('search', '', PARAM_RAW); // Search words. Shortname, fullname, idnumber and summary get searched.
38$blocklist = optional_param('blocklist', 0, PARAM_INT); // Find courses containing this block.
39$modulelist = optional_param('modulelist', '', PARAM_PLUGIN); // Find courses containing the given modules.
40
484c4c6c
SH
41if (!in_array($viewmode, array('default', 'combined', 'courses', 'categories'))) {
42 $viewmode = 'default';
43}
44
5dc361e1
SH
45$issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== '');
46if ($issearching) {
47 $viewmode = 'courses';
48}
49
50$url = new moodle_url('/course/management.php');
b488058f 51$systemcontext = $context = context_system::instance();
5dc361e1
SH
52if ($courseid) {
53 $record = get_course($courseid);
442f12f8
MG
54 $course = new core_course_list_element($record);
55 $category = core_course_category::get($course->category);
5dc361e1 56 $categoryid = $category->id;
b488058f 57 $context = context_coursecat::instance($category->id);
d5814f4e 58 $url->param('categoryid', $categoryid);
5dc361e1 59 $url->param('courseid', $course->id);
d5814f4e 60
5dc361e1
SH
61} else if ($categoryid) {
62 $courseid = null;
63 $course = null;
442f12f8 64 $category = core_course_category::get($categoryid);
b488058f 65 $context = context_coursecat::instance($category->id);
5dc361e1 66 $url->param('categoryid', $category->id);
d5814f4e 67
5dc361e1
SH
68} else {
69 $course = null;
70 $courseid = null;
beff3806 71 $topchildren = core_course_category::top()->get_children();
3b75d143
MJ
72 if (empty($topchildren)) {
73 throw new moodle_exception('cannotviewcategory', 'error');
74 }
beff3806 75 $category = reset($topchildren);
3b75d143 76 $categoryid = $category->id;
fa4f0de6
EL
77 $context = context_coursecat::instance($category->id);
78 $url->param('categoryid', $category->id);
5dc361e1 79}
484c4c6c 80
cda49969
SH
81// Check if there is a selected category param, and if there is apply it.
82if ($course === null && $selectedcategoryid !== null && $selectedcategoryid !== $categoryid) {
83 $url->param('categoryid', $selectedcategoryid);
84}
85
5dc361e1
SH
86if ($page !== 0) {
87 $url->param('page', $page);
88}
89if ($viewmode !== 'default') {
90 $url->param('view', $viewmode);
91}
92if ($search !== '') {
93 $url->param('search', $search);
94}
95if ($blocklist !== 0) {
96 $url->param('blocklist', $search);
97}
98if ($modulelist !== '') {
99 $url->param('modulelist', $search);
100}
101
62846237 102$strmanagement = new lang_string('coursecatmanagement');
52c93a26 103$pageheading = format_string($SITE->fullname, true, array('context' => $systemcontext));
5dc361e1
SH
104
105$PAGE->set_context($context);
106$PAGE->set_url($url);
107$PAGE->set_pagelayout('admin');
52c93a26
SH
108$PAGE->set_title($strmanagement);
109$PAGE->set_heading($pageheading);
5dc361e1 110
962885e8
SH
111// This is a system level page that operates on other contexts.
112require_login();
113
442f12f8 114if (!core_course_category::has_capability_on_any(array('moodle/category:manage', 'moodle/course:create'))) {
484c4c6c
SH
115 // The user isn't able to manage any categories. Lets redirect them to the relevant course/index.php page.
116 $url = new moodle_url('/course/index.php');
117 if ($categoryid) {
118 $url->param('categoryid', $categoryid);
119 }
120 redirect($url);
121}
122
b488058f
SH
123// If the user poses any of these capabilities then they will be able to see the admin
124// tree and the management link within it.
125// This is the most accurate form of navigation.
126$capabilities = array(
127 'moodle/site:config',
128 'moodle/backup:backupcourse',
129 'moodle/category:manage',
130 'moodle/course:create',
131 'moodle/site:approvecourse'
132);
133if ($category && !has_any_capability($capabilities, $systemcontext)) {
484c4c6c
SH
134 // If the user doesn't poses any of these system capabilities then we're going to mark the manage link in the settings block
135 // as active, tell the page to ignore the active path and just build what the user would expect.
b488058f 136 // This will at least give the page some relevant navigation.
b488058f 137 navigation_node::override_active_url(new moodle_url('/course/management.php', array('categoryid' => $category->id)));
484c4c6c
SH
138 $PAGE->set_category_by_id($category->id);
139 $PAGE->navbar->ignore_active(true);
140 $PAGE->navbar->add(get_string('coursemgmt', 'admin'), $PAGE->url->out_omit_querystring());
9fcd0290
MG
141} else {
142 // If user has system capabilities, make sure the "Manage courses and categories" item in Administration block is active.
143 navigation_node::require_admin_tree();
144 navigation_node::override_active_url(new moodle_url('/course/management.php'));
484c4c6c
SH
145}
146if (!$issearching && $category !== null) {
442f12f8 147 $parents = core_course_category::get_many($category->get_parents());
484c4c6c 148 $parents[] = $category;
5dc361e1 149 foreach ($parents as $parent) {
484c4c6c
SH
150 $PAGE->navbar->add(
151 $parent->get_formatted_name(),
152 new moodle_url('/course/management.php', array('categoryid' => $parent->id))
153 );
5dc361e1 154 }
442f12f8 155 if ($course instanceof core_course_list_element) {
5dc361e1
SH
156 // Use the list name so that it matches whats being displayed below.
157 $PAGE->navbar->add($course->get_formatted_name());
158 }
159}
160
5dc361e1
SH
161$notificationspass = array();
162$notificationsfail = array();
163
164if ($action !== false && confirm_sesskey()) {
165 // Actions:
166 // - resortcategories : Resort the courses in the given category.
167 // - resortcourses : Resort courses
168 // - showcourse : make a course visible.
169 // - hidecourse : make a course hidden.
170 // - movecourseup : move the selected course up one.
171 // - movecoursedown : move the selected course down.
172 // - showcategory : make a category visible.
173 // - hidecategory : make a category hidden.
174 // - movecategoryup : move category up.
175 // - movecategorydown : move category down.
176 // - deletecategory : delete the category either in full, or moving contents.
177 // - bulkaction : performs bulk actions:
178 // - bulkmovecourses.
179 // - bulkmovecategories.
180 // - bulkresortcategories.
181 $redirectback = false;
38a15200 182 $redirectmessage = false;
5dc361e1
SH
183 switch ($action) {
184 case 'resortcategories' :
185 $sort = required_param('resort', PARAM_ALPHA);
442f12f8 186 $cattosort = core_course_category::get((int)optional_param('categoryid', 0, PARAM_INT));
b488058f 187 $redirectback = \core_course\management\helper::action_category_resort_subcategories($cattosort, $sort);
5dc361e1
SH
188 break;
189 case 'resortcourses' :
190 // They must have specified a category.
191 required_param('categoryid', PARAM_INT);
192 $sort = required_param('resort', PARAM_ALPHA);
193 \core_course\management\helper::action_category_resort_courses($category, $sort);
194 break;
195 case 'showcourse' :
196 $redirectback = \core_course\management\helper::action_course_show($course);
197 break;
198 case 'hidecourse' :
199 $redirectback = \core_course\management\helper::action_course_hide($course);
200 break;
201 case 'movecourseup' :
202 // They must have specified a category and a course.
203 required_param('categoryid', PARAM_INT);
204 required_param('courseid', PARAM_INT);
5aff38e4 205 $redirectback = \core_course\management\helper::action_course_change_sortorder_up_one($course, $category);
5dc361e1
SH
206 break;
207 case 'movecoursedown' :
208 // They must have specified a category and a course.
209 required_param('categoryid', PARAM_INT);
210 required_param('courseid', PARAM_INT);
5aff38e4 211 $redirectback = \core_course\management\helper::action_course_change_sortorder_down_one($course, $category);
5dc361e1
SH
212 break;
213 case 'showcategory' :
214 // They must have specified a category.
215 required_param('categoryid', PARAM_INT);
216 $redirectback = \core_course\management\helper::action_category_show($category);
217 break;
218 case 'hidecategory' :
219 // They must have specified a category.
220 required_param('categoryid', PARAM_INT);
221 $redirectback = \core_course\management\helper::action_category_hide($category);
222 break;
223 case 'movecategoryup' :
224 // They must have specified a category.
225 required_param('categoryid', PARAM_INT);
5aff38e4 226 $redirectback = \core_course\management\helper::action_category_change_sortorder_up_one($category);
5dc361e1
SH
227 break;
228 case 'movecategorydown' :
229 // They must have specified a category.
230 required_param('categoryid', PARAM_INT);
5aff38e4 231 $redirectback = \core_course\management\helper::action_category_change_sortorder_down_one($category);
5dc361e1
SH
232 break;
233 case 'deletecategory':
234 // They must have specified a category.
235 required_param('categoryid', PARAM_INT);
236 if (!$category->can_delete()) {
442f12f8 237 throw new moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
5dc361e1 238 }
67e1f268 239 $mform = new core_course_deletecategory_form(null, $category);
5dc361e1 240 if ($mform->is_cancelled()) {
484c4c6c 241 redirect($PAGE->url);
5dc361e1
SH
242 }
243 // Start output.
244 /* @var core_course_management_renderer|core_renderer $renderer */
245 $renderer = $PAGE->get_renderer('core_course', 'management');
246 echo $renderer->header();
247 echo $renderer->heading(get_string('deletecategory', 'moodle', $category->get_formatted_name()));
248
249 if ($data = $mform->get_data()) {
250 // The form has been submit handle it.
251 if ($data->fulldelete == 1 && $category->can_delete_full()) {
8aa3aa3d
SH
252 $continueurl = new moodle_url('/course/management.php');
253 if ($category->parent != '0') {
254 $continueurl->param('categoryid', $category->parent);
255 }
5dc361e1
SH
256 $notification = get_string('coursecategorydeleted', '', $category->get_formatted_name());
257 $deletedcourses = $category->delete_full(true);
258 foreach ($deletedcourses as $course) {
259 echo $renderer->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess');
260 }
261 echo $renderer->notification($notification, 'notifysuccess');
262 echo $renderer->continue_button($continueurl);
263 } else if ($data->fulldelete == 0 && $category->can_move_content_to($data->newparent)) {
264 $continueurl = new moodle_url('/course/management.php', array('categoryid' => $data->newparent));
265 $category->delete_move($data->newparent, true);
266 echo $renderer->continue_button($continueurl);
267 } else {
268 // Some error in parameters (user is cheating?)
269 $mform->display();
270 }
271 } else {
272 // Display the form.
273 $mform->display();
274 }
275 // Finish output and exit.
276 echo $renderer->footer();
277 exit();
278 break;
279 case 'bulkaction':
280 $bulkmovecourses = optional_param('bulkmovecourses', false, PARAM_BOOL);
281 $bulkmovecategories = optional_param('bulkmovecategories', false, PARAM_BOOL);
c7a2291f 282 $bulkresortcategories = optional_param('bulksort', false, PARAM_BOOL);
5dc361e1
SH
283
284 if ($bulkmovecourses) {
285 // Move courses out of the current category and into a new category.
286 // They must have specified a category.
287 required_param('categoryid', PARAM_INT);
288 $movetoid = required_param('movecoursesto', PARAM_INT);
289 $courseids = optional_param_array('bc', false, PARAM_INT);
290 if ($courseids === false) {
291 break;
292 }
442f12f8 293 $moveto = core_course_category::get($movetoid);
67e1f268
SH
294 try {
295 // If this fails we want to catch the exception and report it.
fe3999ec 296 $redirectback = \core_course\management\helper::move_courses_into_category($moveto,
67e1f268 297 $courseids);
38a15200
SH
298 if ($redirectback) {
299 $a = new stdClass;
300 $a->category = $moveto->get_formatted_name();
301 $a->courses = count($courseids);
302 $redirectmessage = get_string('bulkmovecoursessuccess', 'moodle', $a);
303 }
67e1f268
SH
304 } catch (moodle_exception $ex) {
305 $redirectback = false;
306 $notificationsfail[] = $ex->getMessage();
307 }
5dc361e1 308 } else if ($bulkmovecategories) {
d61c05ac 309 $categoryids = optional_param_array('bcat', array(), PARAM_INT);
5dc361e1 310 $movetocatid = required_param('movecategoriesto', PARAM_INT);
442f12f8 311 $movetocat = core_course_category::get($movetocatid);
5dc361e1
SH
312 $movecount = 0;
313 foreach ($categoryids as $id) {
442f12f8 314 $cattomove = core_course_category::get($id);
5dc361e1
SH
315 if ($id == $movetocatid) {
316 $notificationsfail[] = get_string('movecategoryownparent', 'error', $cattomove->get_formatted_name());
317 continue;
318 }
47ff771e
PH
319 // Don't allow user to move selected category into one of it's own sub-categories.
320 if (strpos($movetocat->path, $cattomove->path . '/') === 0) {
5dc361e1
SH
321 $notificationsfail[] = get_string('movecategoryparentconflict', 'error', $cattomove->get_formatted_name());
322 continue;
323 }
324 if ($cattomove->parent != $movetocatid) {
325 if ($cattomove->can_change_parent($movetocatid)) {
326 $cattomove->change_parent($movetocatid);
327 $movecount++;
328 } else {
329 $notificationsfail[] = get_string('movecategorynotpossible', 'error', $cattomove->get_formatted_name());
330 }
331 }
332 }
333 if ($movecount > 1) {
334 $a = new stdClass;
335 $a->count = $movecount;
336 $a->to = $movetocat->get_formatted_name();
3b732cd6
RT
337 $movesuccessstrkey = 'movecategoriessuccess';
338 if ($movetocatid == 0) {
339 $movesuccessstrkey = 'movecategoriestotopsuccess';
340 }
341 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
5dc361e1
SH
342 } else if ($movecount === 1) {
343 $a = new stdClass;
344 $a->moved = $cattomove->get_formatted_name();
345 $a->to = $movetocat->get_formatted_name();
3b732cd6
RT
346 $movesuccessstrkey = 'movecategorysuccess';
347 if ($movetocatid == 0) {
348 $movesuccessstrkey = 'movecategorytotopsuccess';
349 }
350 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
5dc361e1
SH
351 }
352 } else if ($bulkresortcategories) {
c7a2291f
SH
353 $for = required_param('selectsortby', PARAM_ALPHA);
354 $sortcategoriesby = required_param('resortcategoriesby', PARAM_ALPHA);
355 $sortcoursesby = required_param('resortcoursesby', PARAM_ALPHA);
356
357 if ($sortcategoriesby === 'none' && $sortcoursesby === 'none') {
358 // They're not sorting anything.
359 break;
360 }
9a4231e9
S
361 if (!in_array($sortcategoriesby, array('idnumber', 'idnumberdesc',
362 'name', 'namedesc'))) {
38a15200
SH
363 $sortcategoriesby = false;
364 }
9a4231e9
S
365 if (!in_array($sortcoursesby, array('timecreated', 'timecreateddesc',
366 'idnumber', 'idnumberdesc',
367 'fullname', 'fullnamedesc',
368 'shortname', 'shortnamedesc'))) {
38a15200
SH
369 $sortcoursesby = false;
370 }
c7a2291f
SH
371
372 if ($for === 'thiscategory') {
373 $categoryids = array(
374 required_param('currentcategoryid', PARAM_INT)
375 );
442f12f8 376 $categories = core_course_category::get_many($categoryids);
c7a2291f
SH
377 } else if ($for === 'selectedcategories') {
378 // Bulk resort selected categories.
379 $categoryids = optional_param_array('bcat', false, PARAM_INT);
380 $sort = required_param('resortcategoriesby', PARAM_ALPHA);
381 if ($categoryids === false) {
382 break;
383 }
442f12f8 384 $categories = core_course_category::get_many($categoryids);
c7a2291f 385 } else if ($for === 'allcategories') {
beff3806 386 if ($sortcategoriesby && core_course_category::top()->can_resort_subcategories()) {
442f12f8 387 \core_course\management\helper::action_category_resort_subcategories(
beff3806 388 core_course_category::top(), $sortcategoriesby);
38a15200 389 }
442f12f8 390 $categorieslist = core_course_category::make_categories_list('moodle/category:manage');
38a15200 391 $categoryids = array_keys($categorieslist);
442f12f8 392 $categories = core_course_category::get_many($categoryids);
38a15200 393 unset($categorieslist);
c7a2291f 394 } else {
5dc361e1
SH
395 break;
396 }
5dc361e1 397 foreach ($categories as $cat) {
c820e700 398 if ($sortcategoriesby && $cat->can_resort_subcategories()) {
c7a2291f
SH
399 // Don't clean up here, we'll do it once we're all done.
400 \core_course\management\helper::action_category_resort_subcategories($cat, $sortcategoriesby, false);
401 }
c820e700 402 if ($sortcoursesby && $cat->can_resort_courses()) {
c7a2291f
SH
403 \core_course\management\helper::action_category_resort_courses($cat, $sortcoursesby, false);
404 }
5dc361e1 405 }
442f12f8 406 core_course_category::resort_categories_cleanup($sortcoursesby !== false);
b488058f
SH
407 if ($category === null && count($categoryids) === 1) {
408 // They're bulk sorting just a single category and they've not selected a category.
409 // Lets for convenience sake auto-select the category that has been resorted for them.
410 redirect(new moodle_url($PAGE->url, array('categoryid' => reset($categoryids))));
411 }
5dc361e1
SH
412 }
413 }
414 if ($redirectback) {
38a15200
SH
415 if ($redirectmessage) {
416 redirect($PAGE->url, $redirectmessage, 5);
417 } else {
418 redirect($PAGE->url);
419 }
5dc361e1
SH
420 }
421}
422
423if (!is_null($perpage)) {
424 set_user_preference('coursecat_management_perpage', $perpage);
425} else {
426 $perpage = get_user_preferences('coursecat_management_perpage', $CFG->coursesperpage);
427}
428if ((int)$perpage != $perpage || $perpage < 2) {
429 $perpage = $CFG->coursesperpage;
430}
431
432$categorysize = 4;
433$coursesize = 4;
434$detailssize = 4;
435if ($viewmode === 'default' || $viewmode === 'combined') {
436 if (isset($courseid)) {
437 $class = 'columns-3';
438 } else {
439 $categorysize = 5;
440 $coursesize = 7;
441 $class = 'columns-2';
442 }
443} else if ($viewmode === 'categories') {
444 $categorysize = 12;
445 $class = 'columns-1';
446} else if ($viewmode === 'courses') {
447 if (isset($courseid)) {
448 $coursesize = 6;
449 $detailssize = 6;
450 $class = 'columns-2';
451 } else {
452 $coursesize = 12;
453 $class = 'columns-1';
454 }
455}
484c4c6c
SH
456if ($viewmode === 'default' || $viewmode === 'combined') {
457 $class .= ' viewmode-cobmined';
458} else {
459 $class .= ' viewmode-'.$viewmode;
460}
461if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) {
462 $class .= ' course-selected';
463}
5dc361e1
SH
464
465/* @var core_course_management_renderer|core_renderer $renderer */
466$renderer = $PAGE->get_renderer('core_course', 'management');
467$renderer->enhance_management_interface();
468
d0647301
SH
469$displaycategorylisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories');
470$displaycourselisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses');
471$displaycoursedetail = (isset($courseid));
472
5dc361e1
SH
473echo $renderer->header();
474
475if (!$issearching) {
62846237 476 echo $renderer->management_heading($strmanagement, $viewmode, $categoryid);
5dc361e1
SH
477} else {
478 echo $renderer->management_heading(new lang_string('searchresults'));
479}
480
481if (count($notificationspass) > 0) {
482 echo $renderer->notification(join('<br />', $notificationspass), 'notifysuccess');
483}
484if (count($notificationsfail) > 0) {
485 echo $renderer->notification(join('<br />', $notificationsfail));
486}
487
488// Start the management form.
489echo $renderer->management_form_start();
490
d0647301
SH
491echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail);
492
a1451599
SH
493echo $renderer->grid_start('course-category-listings', $class);
494
d0647301 495if ($displaycategorylisting) {
5dc361e1
SH
496 echo $renderer->grid_column_start($categorysize, 'category-listing');
497 echo $renderer->category_listing($category);
498 echo $renderer->grid_column_end();
499}
d0647301 500if ($displaycourselisting) {
5dc361e1
SH
501 echo $renderer->grid_column_start($coursesize, 'course-listing');
502 if (!$issearching) {
d29bde59 503 echo $renderer->course_listing($category, $course, $page, $perpage, $viewmode);
5dc361e1
SH
504 } else {
505 list($courses, $coursescount, $coursestotal) =
506 \core_course\management\helper::search_courses($search, $blocklist, $modulelist, $page, $perpage);
09fef88c 507 echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage, $search);
5dc361e1
SH
508 }
509 echo $renderer->grid_column_end();
d0647301 510 if ($displaycoursedetail) {
5dc361e1
SH
511 echo $renderer->grid_column_start($detailssize, 'course-detail');
512 echo $renderer->course_detail($course);
513 echo $renderer->grid_column_end();
514 }
515}
516echo $renderer->grid_end();
517
5dc361e1
SH
518// End of the management form.
519echo $renderer->management_form_end();
4b07116a
WL
520echo $renderer->course_search_form($search);
521
d61c05ac 522echo $renderer->footer();