Merge branch 'wip_master_mdl-42723' of https://github.com/iarenaza/moodle
[moodle.git] / course / management.php
blob086ff0093a01003147217a937da9446dc33eb593
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.'/lib/coursecatlib.php');
27 require_once($CFG->dirroot.'/course/lib.php');
29 $categoryid = optional_param('categoryid', null, PARAM_INT);
30 $selectedcategoryid = optional_param('selectedcategoryid', null, PARAM_INT);
31 $courseid = optional_param('courseid', null, PARAM_INT);
32 $action = optional_param('action', false, PARAM_ALPHA);
33 $page = optional_param('page', 0, PARAM_INT);
34 $perpage = optional_param('perpage', null, PARAM_INT);
35 $viewmode = optional_param('view', 'default', PARAM_ALPHA); // Can be one of default, combined, courses, or categories.
37 // Search related params.
38 $search = optional_param('search', '', PARAM_RAW); // Search words. Shortname, fullname, idnumber and summary get searched.
39 $blocklist = optional_param('blocklist', 0, PARAM_INT); // Find courses containing this block.
40 $modulelist = optional_param('modulelist', '', PARAM_PLUGIN); // Find courses containing the given modules.
42 if (!in_array($viewmode, array('default', 'combined', 'courses', 'categories'))) {
43 $viewmode = 'default';
46 $issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== '');
47 if ($issearching) {
48 $viewmode = 'courses';
51 $url = new moodle_url('/course/management.php');
52 $systemcontext = $context = context_system::instance();
53 if ($courseid) {
54 $record = get_course($courseid);
55 $course = new course_in_list($record);
56 $category = coursecat::get($course->category);
57 $categoryid = $category->id;
58 $context = context_coursecat::instance($category->id);
59 $url->param('categoryid', $categoryid);
60 navigation_node::override_active_url($url);
61 $url->param('courseid', $course->id);
63 } else if ($categoryid) {
64 $courseid = null;
65 $course = null;
66 $category = coursecat::get($categoryid);
67 $context = context_coursecat::instance($category->id);
68 $url->param('categoryid', $category->id);
69 navigation_node::override_active_url($url);
71 } else {
72 $course = null;
73 $courseid = null;
74 $category = null;
75 $categoryid = null;
76 if ($viewmode === 'default') {
77 $viewmode = 'categories';
79 $context = $systemcontext;
80 navigation_node::override_active_url($url);
83 // Check if there is a selected category param, and if there is apply it.
84 if ($course === null && $selectedcategoryid !== null && $selectedcategoryid !== $categoryid) {
85 $url->param('categoryid', $selectedcategoryid);
88 if ($page !== 0) {
89 $url->param('page', $page);
91 if ($viewmode !== 'default') {
92 $url->param('view', $viewmode);
94 if ($search !== '') {
95 $url->param('search', $search);
97 if ($blocklist !== 0) {
98 $url->param('blocklist', $search);
100 if ($modulelist !== '') {
101 $url->param('modulelist', $search);
104 $strmanagement = new lang_string('coursecatmanagement');
105 $pageheading = format_string($SITE->fullname, true, array('context' => $systemcontext));
107 $PAGE->set_context($context);
108 $PAGE->set_url($url);
109 $PAGE->set_pagelayout('admin');
110 $PAGE->set_title($strmanagement);
111 $PAGE->set_heading($pageheading);
113 // This is a system level page that operates on other contexts.
114 require_login();
116 if (!coursecat::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 the user poses any of these capabilities then they will be able to see the admin
126 // tree and the management link within it.
127 // This is the most accurate form of navigation.
128 $capabilities = array(
129 'moodle/site:config',
130 'moodle/backup:backupcourse',
131 'moodle/category:manage',
132 'moodle/course:create',
133 'moodle/site:approvecourse'
135 if ($category && !has_any_capability($capabilities, $systemcontext)) {
136 // If the user doesn't poses any of these system capabilities then we're going to mark the manage link in the settings block
137 // as active, tell the page to ignore the active path and just build what the user would expect.
138 // This will at least give the page some relevant navigation.
139 navigation_node::override_active_url(new moodle_url('/course/management.php', array('categoryid' => $category->id)));
140 $PAGE->set_category_by_id($category->id);
141 $PAGE->navbar->ignore_active(true);
142 $PAGE->navbar->add(get_string('coursemgmt', 'admin'), $PAGE->url->out_omit_querystring());
144 if (!$issearching && $category !== null) {
145 $parents = coursecat::get_many($category->get_parents());
146 $parents[] = $category;
147 foreach ($parents as $parent) {
148 $PAGE->navbar->add(
149 $parent->get_formatted_name(),
150 new moodle_url('/course/management.php', array('categoryid' => $parent->id))
153 if ($course instanceof course_in_list) {
154 // Use the list name so that it matches whats being displayed below.
155 $PAGE->navbar->add($course->get_formatted_name());
159 $notificationspass = array();
160 $notificationsfail = array();
162 if ($action !== false && confirm_sesskey()) {
163 // Actions:
164 // - resortcategories : Resort the courses in the given category.
165 // - resortcourses : Resort courses
166 // - showcourse : make a course visible.
167 // - hidecourse : make a course hidden.
168 // - movecourseup : move the selected course up one.
169 // - movecoursedown : move the selected course down.
170 // - showcategory : make a category visible.
171 // - hidecategory : make a category hidden.
172 // - movecategoryup : move category up.
173 // - movecategorydown : move category down.
174 // - deletecategory : delete the category either in full, or moving contents.
175 // - bulkaction : performs bulk actions:
176 // - bulkmovecourses.
177 // - bulkmovecategories.
178 // - bulkresortcategories.
179 $redirectback = false;
180 $redirectmessage = false;
181 switch ($action) {
182 case 'resortcategories' :
183 $sort = required_param('resort', PARAM_ALPHA);
184 $cattosort = coursecat::get((int)optional_param('categoryid', 0, PARAM_INT));
185 $redirectback = \core_course\management\helper::action_category_resort_subcategories($cattosort, $sort);
186 break;
187 case 'resortcourses' :
188 // They must have specified a category.
189 required_param('categoryid', PARAM_INT);
190 $sort = required_param('resort', PARAM_ALPHA);
191 \core_course\management\helper::action_category_resort_courses($category, $sort);
192 break;
193 case 'showcourse' :
194 $redirectback = \core_course\management\helper::action_course_show($course);
195 break;
196 case 'hidecourse' :
197 $redirectback = \core_course\management\helper::action_course_hide($course);
198 break;
199 case 'movecourseup' :
200 // They must have specified a category and a course.
201 required_param('categoryid', PARAM_INT);
202 required_param('courseid', PARAM_INT);
203 $redirectback = \core_course\management\helper::action_course_change_sortorder_up_one($course, $category);
204 break;
205 case 'movecoursedown' :
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_down_one($course, $category);
210 break;
211 case 'showcategory' :
212 // They must have specified a category.
213 required_param('categoryid', PARAM_INT);
214 $redirectback = \core_course\management\helper::action_category_show($category);
215 break;
216 case 'hidecategory' :
217 // They must have specified a category.
218 required_param('categoryid', PARAM_INT);
219 $redirectback = \core_course\management\helper::action_category_hide($category);
220 break;
221 case 'movecategoryup' :
222 // They must have specified a category.
223 required_param('categoryid', PARAM_INT);
224 $redirectback = \core_course\management\helper::action_category_change_sortorder_up_one($category);
225 break;
226 case 'movecategorydown' :
227 // They must have specified a category.
228 required_param('categoryid', PARAM_INT);
229 $redirectback = \core_course\management\helper::action_category_change_sortorder_down_one($category);
230 break;
231 case 'deletecategory':
232 // They must have specified a category.
233 required_param('categoryid', PARAM_INT);
234 if (!$category->can_delete()) {
235 throw new moodle_exception('permissiondenied', 'error', '', null, 'coursecat::can_resort');
237 require_once($CFG->dirroot.'/course/delete_category_form.php');
238 $mform = new core_course_deletecategory_form(null, $category);
239 if ($mform->is_cancelled()) {
240 redirect($PAGE->url);
242 // Start output.
243 /* @var core_course_management_renderer|core_renderer $renderer */
244 $renderer = $PAGE->get_renderer('core_course', 'management');
245 echo $renderer->header();
246 echo $renderer->heading(get_string('deletecategory', 'moodle', $category->get_formatted_name()));
248 if ($data = $mform->get_data()) {
249 // The form has been submit handle it.
250 if ($data->fulldelete == 1 && $category->can_delete_full()) {
251 $continueurl = new moodle_url('/course/management.php');
252 if ($category->parent != '0') {
253 $continueurl->param('categoryid', $category->parent);
255 $notification = get_string('coursecategorydeleted', '', $category->get_formatted_name());
256 $deletedcourses = $category->delete_full(true);
257 foreach ($deletedcourses as $course) {
258 echo $renderer->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess');
260 echo $renderer->notification($notification, 'notifysuccess');
261 echo $renderer->continue_button($continueurl);
262 } else if ($data->fulldelete == 0 && $category->can_move_content_to($data->newparent)) {
263 $continueurl = new moodle_url('/course/management.php', array('categoryid' => $data->newparent));
264 $category->delete_move($data->newparent, true);
265 echo $renderer->continue_button($continueurl);
266 } else {
267 // Some error in parameters (user is cheating?)
268 $mform->display();
270 } else {
271 // Display the form.
272 $mform->display();
274 // Finish output and exit.
275 echo $renderer->footer();
276 exit();
277 break;
278 case 'bulkaction':
279 $bulkmovecourses = optional_param('bulkmovecourses', false, PARAM_BOOL);
280 $bulkmovecategories = optional_param('bulkmovecategories', false, PARAM_BOOL);
281 $bulkresortcategories = optional_param('bulksort', false, PARAM_BOOL);
283 if ($bulkmovecourses) {
284 // Move courses out of the current category and into a new category.
285 // They must have specified a category.
286 required_param('categoryid', PARAM_INT);
287 $movetoid = required_param('movecoursesto', PARAM_INT);
288 $courseids = optional_param_array('bc', false, PARAM_INT);
289 if ($courseids === false) {
290 break;
292 $moveto = coursecat::get($movetoid);
293 try {
294 // If this fails we want to catch the exception and report it.
295 $redirectback = \core_course\management\helper::action_category_move_courses_into($category, $moveto,
296 $courseids);
297 if ($redirectback) {
298 $a = new stdClass;
299 $a->category = $moveto->get_formatted_name();
300 $a->courses = count($courseids);
301 $redirectmessage = get_string('bulkmovecoursessuccess', 'moodle', $a);
303 } catch (moodle_exception $ex) {
304 $redirectback = false;
305 $notificationsfail[] = $ex->getMessage();
307 } else if ($bulkmovecategories) {
308 $categoryids = optional_param_array('bcat', array(), PARAM_INT);
309 $movetocatid = required_param('movecategoriesto', PARAM_INT);
310 $movetocat = coursecat::get($movetocatid);
311 $movecount = 0;
312 foreach ($categoryids as $id) {
313 $cattomove = coursecat::get($id);
314 if ($id == $movetocatid) {
315 $notificationsfail[] = get_string('movecategoryownparent', 'error', $cattomove->get_formatted_name());
316 continue;
318 if (strpos($movetocat->path, $cattomove->path) === 0) {
319 $notificationsfail[] = get_string('movecategoryparentconflict', 'error', $cattomove->get_formatted_name());
320 continue;
322 if ($cattomove->parent != $movetocatid) {
323 if ($cattomove->can_change_parent($movetocatid)) {
324 $cattomove->change_parent($movetocatid);
325 $movecount++;
326 } else {
327 $notificationsfail[] = get_string('movecategorynotpossible', 'error', $cattomove->get_formatted_name());
331 if ($movecount > 1) {
332 $a = new stdClass;
333 $a->count = $movecount;
334 $a->to = $movetocat->get_formatted_name();
335 $movesuccessstrkey = 'movecategoriessuccess';
336 if ($movetocatid == 0) {
337 $movesuccessstrkey = 'movecategoriestotopsuccess';
339 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
340 } else if ($movecount === 1) {
341 $a = new stdClass;
342 $a->moved = $cattomove->get_formatted_name();
343 $a->to = $movetocat->get_formatted_name();
344 $movesuccessstrkey = 'movecategorysuccess';
345 if ($movetocatid == 0) {
346 $movesuccessstrkey = 'movecategorytotopsuccess';
348 $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
350 } else if ($bulkresortcategories) {
351 $for = required_param('selectsortby', PARAM_ALPHA);
352 $sortcategoriesby = required_param('resortcategoriesby', PARAM_ALPHA);
353 $sortcoursesby = required_param('resortcoursesby', PARAM_ALPHA);
355 if ($sortcategoriesby === 'none' && $sortcoursesby === 'none') {
356 // They're not sorting anything.
357 break;
359 if (!in_array($sortcategoriesby, array('idnumber', 'name'))) {
360 $sortcategoriesby = false;
362 if (!in_array($sortcoursesby, array('idnumber', 'fullname', 'shortname'))) {
363 $sortcoursesby = false;
366 if ($for === 'thiscategory') {
367 $categoryids = array(
368 required_param('currentcategoryid', PARAM_INT)
370 $categories = coursecat::get_many($categoryids);
371 } else if ($for === 'selectedcategories') {
372 // Bulk resort selected categories.
373 $categoryids = optional_param_array('bcat', false, PARAM_INT);
374 $sort = required_param('resortcategoriesby', PARAM_ALPHA);
375 if ($categoryids === false) {
376 break;
378 $categories = coursecat::get_many($categoryids);
379 } else if ($for === 'allcategories') {
380 if ($sortcategoriesby && coursecat::get(0)->can_resort_subcategories()) {
381 \core_course\management\helper::action_category_resort_subcategories(coursecat::get(0), $sortcategoriesby);
383 $categorieslist = coursecat::make_categories_list('moodle/category:manage');
384 $categoryids = array_keys($categorieslist);
385 $categories = coursecat::get_many($categoryids);
386 unset($categorieslist);
387 } else {
388 break;
390 foreach ($categories as $cat) {
391 if ($sortcategoriesby && $cat->can_resort_subcategories()) {
392 // Don't clean up here, we'll do it once we're all done.
393 \core_course\management\helper::action_category_resort_subcategories($cat, $sortcategoriesby, false);
395 if ($sortcoursesby && $cat->can_resort_courses()) {
396 \core_course\management\helper::action_category_resort_courses($cat, $sortcoursesby, false);
399 coursecat::resort_categories_cleanup($sortcoursesby !== false);
400 if ($category === null && count($categoryids) === 1) {
401 // They're bulk sorting just a single category and they've not selected a category.
402 // Lets for convenience sake auto-select the category that has been resorted for them.
403 redirect(new moodle_url($PAGE->url, array('categoryid' => reset($categoryids))));
407 if ($redirectback) {
408 if ($redirectmessage) {
409 redirect($PAGE->url, $redirectmessage, 5);
410 } else {
411 redirect($PAGE->url);
416 if (!is_null($perpage)) {
417 set_user_preference('coursecat_management_perpage', $perpage);
418 } else {
419 $perpage = get_user_preferences('coursecat_management_perpage', $CFG->coursesperpage);
421 if ((int)$perpage != $perpage || $perpage < 2) {
422 $perpage = $CFG->coursesperpage;
425 $categorysize = 4;
426 $coursesize = 4;
427 $detailssize = 4;
428 if ($viewmode === 'default' || $viewmode === 'combined') {
429 if (isset($courseid)) {
430 $class = 'columns-3';
431 } else {
432 $categorysize = 5;
433 $coursesize = 7;
434 $class = 'columns-2';
436 } else if ($viewmode === 'categories') {
437 $categorysize = 12;
438 $class = 'columns-1';
439 } else if ($viewmode === 'courses') {
440 if (isset($courseid)) {
441 $coursesize = 6;
442 $detailssize = 6;
443 $class = 'columns-2';
444 } else {
445 $coursesize = 12;
446 $class = 'columns-1';
449 if ($viewmode === 'default' || $viewmode === 'combined') {
450 $class .= ' viewmode-cobmined';
451 } else {
452 $class .= ' viewmode-'.$viewmode;
454 if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) {
455 $class .= ' course-selected';
458 /* @var core_course_management_renderer|core_renderer $renderer */
459 $renderer = $PAGE->get_renderer('core_course', 'management');
460 $renderer->enhance_management_interface();
462 $displaycategorylisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories');
463 $displaycourselisting = ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses');
464 $displaycoursedetail = (isset($courseid));
466 echo $renderer->header();
468 if (!$issearching) {
469 echo $renderer->management_heading($strmanagement, $viewmode, $categoryid);
470 } else {
471 echo $renderer->management_heading(new lang_string('searchresults'));
474 if (count($notificationspass) > 0) {
475 echo $renderer->notification(join('<br />', $notificationspass), 'notifysuccess');
477 if (count($notificationsfail) > 0) {
478 echo $renderer->notification(join('<br />', $notificationsfail));
481 // Start the management form.
482 echo $renderer->management_form_start();
484 echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail);
486 echo $renderer->grid_start('course-category-listings', $class);
488 if ($displaycategorylisting) {
489 echo $renderer->grid_column_start($categorysize, 'category-listing');
490 echo $renderer->category_listing($category);
491 echo $renderer->grid_column_end();
493 if ($displaycourselisting) {
494 echo $renderer->grid_column_start($coursesize, 'course-listing');
495 if (!$issearching) {
496 echo $renderer->course_listing($category, $course, $page, $perpage);
497 } else {
498 list($courses, $coursescount, $coursestotal) =
499 \core_course\management\helper::search_courses($search, $blocklist, $modulelist, $page, $perpage);
500 echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage);
502 echo $renderer->grid_column_end();
503 if ($displaycoursedetail) {
504 echo $renderer->grid_column_start($detailssize, 'course-detail');
505 echo $renderer->course_detail($course);
506 echo $renderer->grid_column_end();
509 echo $renderer->grid_end();
511 // End of the management form.
512 echo $renderer->management_form_end();
513 echo $renderer->footer();