MDL-78962 core/loadingicon: remove jQuery requirement in the API
[moodle.git] / course / management.php
blobf75595f81b64f570542d1a3c7b07ebe18c4d55b1
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.
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/>.
17 /**
18 * Course and category management interfaces.
20 * @package core_course
21 * @copyright 2013 Sam Hemelryk
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 require_once('../config.php');
26 require_once($CFG->dirroot.'/course/lib.php');
28 $categoryid = optional_param('categoryid', null, PARAM_INT);
29 $selectedcategoryid = optional_param('selectedcategoryid', null, PARAM_INT);
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.
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.
41 if (!in_array($viewmode, array('default', 'combined', 'courses', 'categories'))) {
42 $viewmode = 'default';
45 $issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== '');
46 if ($issearching) {
47 $viewmode = 'courses';
50 $url = new moodle_url('/course/management.php');
51 $systemcontext = $context = context_system::instance();
52 if ($courseid) {
53 $record = get_course($courseid);
54 $course = new core_course_list_element($record);
55 $category = core_course_category::get($course->category);
56 $categoryid = $category->id;
57 $context = context_coursecat::instance($category->id);
58 $url->param('categoryid', $categoryid);
59 $url->param('courseid', $course->id);
61 } else if ($categoryid) {
62 $courseid = null;
63 $course = null;
64 $category = core_course_category::get($categoryid);
65 $context = context_coursecat::instance($category->id);
66 $url->param('categoryid', $category->id);
68 } else {
69 $course = null;
70 $courseid = null;
71 $topchildren = core_course_category::top()->get_children();
72 if (empty($topchildren)) {
73 throw new moodle_exception('cannotviewcategory', 'error');
75 $category = reset($topchildren);
76 $categoryid = $category->id;
77 $context = context_coursecat::instance($category->id);
78 $url->param('categoryid', $category->id);
81 // Check if there is a selected category param, and if there is apply it.
82 if ($course === null && $selectedcategoryid !== null && $selectedcategoryid !== $categoryid) {
83 $url->param('categoryid', $selectedcategoryid);
86 if ($page !== 0) {
87 $url->param('page', $page);
89 if ($viewmode !== 'default') {
90 $url->param('view', $viewmode);
92 if ($search !== '') {
93 $url->param('search', $search);
95 if ($blocklist !== 0) {
96 $url->param('blocklist', $search);
98 if ($modulelist !== '') {
99 $url->param('modulelist', $search);
102 $strmanagement = new lang_string('coursecatmanagement');
103 $pageheading = $category->get_formatted_name();
105 $PAGE->set_context($context);
106 $PAGE->set_url($url);
107 $PAGE->set_pagelayout('admin');
108 $PAGE->set_title($strmanagement);
109 $PAGE->set_heading($pageheading);
110 $PAGE->requires->js_call_amd('core_course/copy_modal', 'init', array($context->id));
111 $PAGE->set_secondary_active_tab('categorymain');
113 // This is a system level page that operates on other contexts.
114 require_login();
116 if (!core_course_category::has_capability_on_any(array('moodle/category:manage', 'moodle/course:create'))) {
117 // The user isn't able to manage any categories. Lets redirect them to the relevant course/index.php page.
118 $url = new moodle_url('/course/index.php');
119 if ($categoryid) {
120 $url->param('categoryid', $categoryid);
122 redirect($url);
125 if (!$issearching && $category !== null) {
126 $parents = core_course_category::get_many($category->get_parents());
127 $parents[] = $category;
128 foreach ($parents as $parent) {
129 $PAGE->navbar->add(
130 $parent->get_formatted_name(),
131 new moodle_url('/course/index.php', array('categoryid' => $parent->id))
134 if ($course instanceof core_course_list_element) {
135 // Use the list name so that it matches whats being displayed below.
136 $PAGE->navbar->add($course->get_formatted_name());
140 // If the user poses any of these capabilities then they will be able to see the admin
141 // tree and the management link within it.
142 // This is the most accurate form of navigation.
143 $capabilities = array(
144 'moodle/site:config',
145 'moodle/backup:backupcourse',
146 'moodle/category:manage',
147 'moodle/course:create',
148 'moodle/site:approvecourse'
150 if ($category && !has_any_capability($capabilities, $systemcontext)) {
151 // If the user doesn't poses any of these system capabilities then we're going to mark the category link in the
152 // settings block as active, tell the page to ignore the active path and just build what the user would expect.
153 // This will at least give the page some relevant navigation.
154 navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryid' => $category->id)));
155 $PAGE->set_category_by_id($category->id);
156 $PAGE->navbar->ignore_active(true);
157 } else {
158 // If user has system capabilities, make sure the "Category" item in Administration block is active.
159 navigation_node::require_admin_tree();
160 navigation_node::override_active_url(new moodle_url('/course/index.php'));
162 $PAGE->navbar->add(get_string('coursemgmt', 'admin'), $PAGE->url->out_omit_querystring());
163 $PAGE->set_primary_active_tab('home');
165 $notificationspass = array();
166 $notificationsfail = array();
168 if ($action !== false && confirm_sesskey()) {
169 // Actions:
170 // - resortcategories : Resort the courses in the given category.
171 // - resortcourses : Resort courses
172 // - showcourse : make a course visible.
173 // - hidecourse : make a course hidden.
174 // - movecourseup : move the selected course up one.
175 // - movecoursedown : move the selected course down.
176 // - showcategory : make a category visible.
177 // - hidecategory : make a category hidden.
178 // - movecategoryup : move category up.
179 // - movecategorydown : move category down.
180 // - deletecategory : delete the category either in full, or moving contents.
181 // - bulkaction : performs bulk actions:
182 // - bulkmovecourses.
183 // - bulkmovecategories.
184 // - bulkresortcategories.
185 $redirectback = false;
186 $redirectmessage = false;
187 switch ($action) {
188 case 'resortcategories' :
189 $sort = required_param('resort', PARAM_ALPHA);
190 $cattosort = core_course_category::get((int)optional_param('categoryid', 0, PARAM_INT));
191 $redirectback = \core_course\management\helper::action_category_resort_subcategories($cattosort, $sort);
192 break;
193 case 'resortcourses' :
194 // They must have specified a category.
195 required_param('categoryid', PARAM_INT);
196 $sort = required_param('resort', PARAM_ALPHA);
197 \core_course\management\helper::action_category_resort_courses($category, $sort);
198 break;
199 case 'showcourse' :
200 $redirectback = \core_course\management\helper::action_course_show($course);
201 break;
202 case 'hidecourse' :
203 $redirectback = \core_course\management\helper::action_course_hide($course);
204 break;
205 case 'movecourseup' :
206 // They must have specified a category and a course.
207 required_param('categoryid', PARAM_INT);
208 required_param('courseid', PARAM_INT);
209 $redirectback = \core_course\management\helper::action_course_change_sortorder_up_one($course, $category);
210 break;
211 case 'movecoursedown' :
212 // They must have specified a category and a course.
213 required_param('categoryid', PARAM_INT);
214 required_param('courseid', PARAM_INT);
215 $redirectback = \core_course\management\helper::action_course_change_sortorder_down_one($course, $category);
216 break;
217 case 'showcategory' :
218 // They must have specified a category.
219 required_param('categoryid', PARAM_INT);
220 $redirectback = \core_course\management\helper::action_category_show($category);
221 break;
222 case 'hidecategory' :
223 // They must have specified a category.
224 required_param('categoryid', PARAM_INT);
225 $redirectback = \core_course\management\helper::action_category_hide($category);
226 break;
227 case 'movecategoryup' :
228 // They must have specified a category.
229 required_param('categoryid', PARAM_INT);
230 $redirectback = \core_course\management\helper::action_category_change_sortorder_up_one($category);
231 break;
232 case 'movecategorydown' :
233 // They must have specified a category.
234 required_param('categoryid', PARAM_INT);
235 $redirectback = \core_course\management\helper::action_category_change_sortorder_down_one($category);
236 break;
237 case 'deletecategory':
238 // They must have specified a category.
239 required_param('categoryid', PARAM_INT);
240 if (!$category->can_delete()) {
241 throw new moodle_exception('permissiondenied', 'error', '', null, 'core_course_category::can_resort');
243 $mform = new core_course_deletecategory_form(null, $category);
244 if ($mform->is_cancelled()) {
245 redirect($PAGE->url);
247 // Start output.
248 /* @var core_course_management_renderer|core_renderer $renderer */
249 $renderer = $PAGE->get_renderer('core_course', 'management');
250 echo $renderer->header();
251 echo $renderer->heading(get_string('deletecategory', 'moodle', $category->get_formatted_name()));
253 if ($data = $mform->get_data()) {
254 // The form has been submit handle it.
255 if ($data->fulldelete == 1 && $category->can_delete_full()) {
256 $continueurl = new moodle_url('/course/management.php');
257 if ($category->parent != '0') {
258 $continueurl->param('categoryid', $category->parent);
260 $notification = get_string('coursecategorydeleted', '', $category->get_formatted_name());
261 $deletedcourses = $category->delete_full(true);
262 foreach ($deletedcourses as $course) {
263 echo $renderer->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess');
265 echo $renderer->notification($notification, 'notifysuccess');
266 echo $renderer->continue_button($continueurl);
267 } else if ($data->fulldelete == 0 && $category->can_move_content_to($data->newparent)) {
268 $continueurl = new moodle_url('/course/management.php', array('categoryid' => $data->newparent));
269 $category->delete_move($data->newparent, true);
270 echo $renderer->continue_button($continueurl);
271 } else {
272 // Some error in parameters (user is cheating?)
273 $mform->display();
275 } else {
276 // Display the form.
277 $mform->display();
279 // Finish output and exit.
280 echo $renderer->footer();
281 exit();
282 break;
283 case 'bulkaction':
284 $bulkmovecourses = optional_param('bulkmovecourses', false, PARAM_BOOL);
285 $bulkmovecategories = optional_param('bulkmovecategories', false, PARAM_BOOL);
286 $bulkresortcategories = optional_param('bulksort', false, PARAM_BOOL);
288 if ($bulkmovecourses) {
289 // Move courses out of the current category and into a new category.
290 // They must have specified a category.
291 required_param('categoryid', PARAM_INT);
292 $movetoid = required_param('movecoursesto', PARAM_INT);
293 $courseids = optional_param_array('bc', false, PARAM_INT);
294 if ($courseids === false) {
295 break;
297 $moveto = core_course_category::get($movetoid);
298 try {
299 // If this fails we want to catch the exception and report it.
300 $redirectback = \core_course\management\helper::move_courses_into_category($moveto,
301 $courseids);
302 if ($redirectback) {
303 $a = new stdClass;
304 $a->category = $moveto->get_formatted_name();
305 $a->courses = count($courseids);
306 $redirectmessage = get_string('bulkmovecoursessuccess', 'moodle', $a);
308 } catch (moodle_exception $ex) {
309 $redirectback = false;
310 $notificationsfail[] = $ex->getMessage();
312 } else if ($bulkmovecategories) {
313 $categoryids = optional_param_array('bcat', array(), PARAM_INT);
314 $movetocatid = required_param('movecategoriesto', PARAM_INT);
315 $movetocat = core_course_category::get($movetocatid);
316 $movecount = 0;
317 foreach ($categoryids as $id) {
318 $cattomove = core_course_category::get($id);
319 if ($id == $movetocatid) {
320 $notificationsfail[] = get_string('movecategoryownparent', 'error', $cattomove->get_formatted_name());
321 continue;
323 // Don't allow user to move selected category into one of it's own sub-categories.
324 if (strpos($movetocat->path, $cattomove->path . '/') === 0) {
325 $notificationsfail[] = get_string('movecategoryparentconflict', 'error', $cattomove->get_formatted_name());
326 continue;
328 if ($cattomove->parent != $movetocatid) {
329 if ($cattomove->can_change_parent($movetocatid)) {
330 $cattomove->change_parent($movetocatid);
331 $movecount++;
332 } else {
333 $notificationsfail[] = get_string('movecategorynotpossible', 'error', $cattomove->get_formatted_name());
337 if ($movecount > 1) {
338 $a = new stdClass;
339 $a->count = $movecount;
340 $a->to = $movetocat->get_formatted_name();
341 $movesuccessstrkey = 'movecategoriessuccess';
342 if ($movetocatid == 0) {
343 $movesuccessstrkey = 'movecategoriestotopsuccess';
345 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
346 } else if ($movecount === 1) {
347 $a = new stdClass;
348 $a->moved = $cattomove->get_formatted_name();
349 $a->to = $movetocat->get_formatted_name();
350 $movesuccessstrkey = 'movecategorysuccess';
351 if ($movetocatid == 0) {
352 $movesuccessstrkey = 'movecategorytotopsuccess';
354 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
356 } else if ($bulkresortcategories) {
357 $for = required_param('selectsortby', PARAM_ALPHA);
358 $sortcategoriesby = required_param('resortcategoriesby', PARAM_ALPHA);
359 $sortcoursesby = required_param('resortcoursesby', PARAM_ALPHA);
361 if ($sortcategoriesby === 'none' && $sortcoursesby === 'none') {
362 // They're not sorting anything.
363 break;
365 if (!in_array($sortcategoriesby, array('idnumber', 'idnumberdesc',
366 'name', 'namedesc'))) {
367 $sortcategoriesby = false;
369 if (!in_array($sortcoursesby, array('timecreated', 'timecreateddesc',
370 'idnumber', 'idnumberdesc',
371 'fullname', 'fullnamedesc',
372 'shortname', 'shortnamedesc'))) {
373 $sortcoursesby = false;
376 if ($for === 'thiscategory') {
377 $categoryids = array(
378 required_param('currentcategoryid', PARAM_INT)
380 $categories = core_course_category::get_many($categoryids);
381 } else if ($for === 'selectedcategories') {
382 // Bulk resort selected categories.
383 $categoryids = optional_param_array('bcat', false, PARAM_INT);
384 $sort = required_param('resortcategoriesby', PARAM_ALPHA);
385 if ($categoryids === false) {
386 break;
388 $categories = core_course_category::get_many($categoryids);
389 } else if ($for === 'allcategories') {
390 if ($sortcategoriesby && core_course_category::top()->can_resort_subcategories()) {
391 \core_course\management\helper::action_category_resort_subcategories(
392 core_course_category::top(), $sortcategoriesby);
394 $categorieslist = core_course_category::make_categories_list('moodle/category:manage');
395 $categoryids = array_keys($categorieslist);
396 $categories = core_course_category::get_many($categoryids);
397 unset($categorieslist);
398 } else {
399 break;
401 foreach ($categories as $cat) {
402 if ($sortcategoriesby && $cat->can_resort_subcategories()) {
403 // Don't clean up here, we'll do it once we're all done.
404 \core_course\management\helper::action_category_resort_subcategories($cat, $sortcategoriesby, false);
406 if ($sortcoursesby && $cat->can_resort_courses()) {
407 \core_course\management\helper::action_category_resort_courses($cat, $sortcoursesby, false);
410 core_course_category::resort_categories_cleanup($sortcoursesby !== false);
411 if ($category === null && count($categoryids) === 1) {
412 // They're bulk sorting just a single category and they've not selected a category.
413 // Lets for convenience sake auto-select the category that has been resorted for them.
414 redirect(new moodle_url($PAGE->url, array('categoryid' => reset($categoryids))));
418 if ($redirectback) {
419 if ($redirectmessage) {
420 redirect($PAGE->url, $redirectmessage, 5);
421 } else {
422 redirect($PAGE->url);
427 if (!is_null($perpage)) {
428 set_user_preference('coursecat_management_perpage', $perpage);
429 } else {
430 $perpage = get_user_preferences('coursecat_management_perpage', $CFG->coursesperpage);
432 if ((int)$perpage != $perpage || $perpage < 2) {
433 $perpage = $CFG->coursesperpage;
436 $categorysize = 4;
437 $coursesize = 4;
438 $detailssize = 4;
439 if ($viewmode === 'default' || $viewmode === 'combined') {
440 if (isset($courseid)) {
441 $class = 'columns-3';
442 } else {
443 $categorysize = 5;
444 $coursesize = 7;
445 $class = 'columns-2';
447 } else if ($viewmode === 'categories') {
448 $categorysize = 12;
449 $class = 'columns-1';
450 } else if ($viewmode === 'courses') {
451 if (isset($courseid)) {
452 $coursesize = 6;
453 $detailssize = 6;
454 $class = 'columns-2';
455 } else {
456 $coursesize = 12;
457 $class = 'columns-1';
460 if ($viewmode === 'default' || $viewmode === 'combined') {
461 $class .= ' viewmode-combined';
462 } else {
463 $class .= ' viewmode-'.$viewmode;
465 if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) {
466 $class .= ' course-selected';
469 /* @var core_course_management_renderer|core_renderer $renderer */
470 $renderer = $PAGE->get_renderer('core_course', 'management');
471 $renderer->enhance_management_interface();
473 $displaycategorylisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories');
474 $displaycourselisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses');
475 $displaycoursedetail = (isset($courseid));
477 echo $renderer->header();
478 $actionbar = new \core_course\output\manage_categories_action_bar($PAGE, $viewmode, $course, $search);
479 echo $renderer->render_action_bar($actionbar);
481 if (count($notificationspass) > 0) {
482 echo $renderer->notification(join('<br />', $notificationspass), 'notifysuccess');
484 if (count($notificationsfail) > 0) {
485 echo $renderer->notification(join('<br />', $notificationsfail));
488 // Start the management form.
490 echo $renderer->management_form_start();
492 echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail);
494 echo $renderer->grid_start('course-category-listings', $class);
496 if ($displaycategorylisting) {
497 echo $renderer->grid_column_start($categorysize, 'category-listing');
498 echo $renderer->category_listing($category);
499 echo $renderer->grid_column_end();
501 if ($displaycourselisting) {
502 echo $renderer->grid_column_start($coursesize, 'course-listing');
503 if (!$issearching) {
504 echo $renderer->course_listing($category, $course, $page, $perpage, $viewmode);
505 } else {
506 list($courses, $coursescount, $coursestotal) =
507 \core_course\management\helper::search_courses($search, $blocklist, $modulelist, $page, $perpage);
508 echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage, $search);
510 echo $renderer->grid_column_end();
511 if ($displaycoursedetail) {
512 echo $renderer->grid_column_start($detailssize, 'course-detail');
513 echo $renderer->course_detail($course);
514 echo $renderer->grid_column_end();
517 echo $renderer->grid_end();
519 // End of the management form.
520 echo $renderer->management_form_end();
522 echo $renderer->footer();