Commit | Line | Data |
---|---|---|
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 | ||
25 | require_once('../config.php'); | |
26 | require_once($CFG->dirroot.'/lib/coursecatlib.php'); | |
27 | require_once($CFG->dirroot.'/course/lib.php'); | |
28 | ||
29 | $categoryid = optional_param('categoryid', null, PARAM_INT); | |
cda49969 | 30 | $selectedcategoryid = optional_param('selectedcategoryid', null, PARAM_INT); |
5dc361e1 SH |
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. | |
36 | ||
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. | |
41 | ||
484c4c6c SH |
42 | if (!in_array($viewmode, array('default', 'combined', 'courses', 'categories'))) { |
43 | $viewmode = 'default'; | |
44 | } | |
45 | ||
5dc361e1 SH |
46 | $issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== ''); |
47 | if ($issearching) { | |
48 | $viewmode = 'courses'; | |
49 | } | |
50 | ||
51 | $url = new moodle_url('/course/management.php'); | |
b488058f | 52 | $systemcontext = $context = context_system::instance(); |
5dc361e1 SH |
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; | |
b488058f | 58 | $context = context_coursecat::instance($category->id); |
d5814f4e PS |
59 | $url->param('categoryid', $categoryid); |
60 | navigation_node::override_active_url($url); | |
5dc361e1 | 61 | $url->param('courseid', $course->id); |
d5814f4e | 62 | |
5dc361e1 SH |
63 | } else if ($categoryid) { |
64 | $courseid = null; | |
65 | $course = null; | |
66 | $category = coursecat::get($categoryid); | |
b488058f | 67 | $context = context_coursecat::instance($category->id); |
5dc361e1 | 68 | $url->param('categoryid', $category->id); |
d5814f4e PS |
69 | navigation_node::override_active_url($url); |
70 | ||
5dc361e1 SH |
71 | } else { |
72 | $course = null; | |
73 | $courseid = null; | |
74 | $category = null; | |
75 | $categoryid = null; | |
76 | if ($viewmode === 'default') { | |
77 | $viewmode = 'categories'; | |
78 | } | |
b488058f | 79 | $context = $systemcontext; |
d5814f4e | 80 | navigation_node::override_active_url($url); |
5dc361e1 | 81 | } |
484c4c6c | 82 | |
cda49969 SH |
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); | |
86 | } | |
87 | ||
5dc361e1 SH |
88 | if ($page !== 0) { |
89 | $url->param('page', $page); | |
90 | } | |
91 | if ($viewmode !== 'default') { | |
92 | $url->param('view', $viewmode); | |
93 | } | |
94 | if ($search !== '') { | |
95 | $url->param('search', $search); | |
96 | } | |
97 | if ($blocklist !== 0) { | |
98 | $url->param('blocklist', $search); | |
99 | } | |
100 | if ($modulelist !== '') { | |
101 | $url->param('modulelist', $search); | |
102 | } | |
103 | ||
62846237 | 104 | $strmanagement = new lang_string('coursecatmanagement'); |
b488058f | 105 | $title = format_string($SITE->fullname, true, array('context' => $systemcontext)); |
5dc361e1 SH |
106 | |
107 | $PAGE->set_context($context); | |
108 | $PAGE->set_url($url); | |
109 | $PAGE->set_pagelayout('admin'); | |
110 | $PAGE->set_title($title); | |
62846237 | 111 | $PAGE->set_heading($strmanagement); |
5dc361e1 | 112 | |
962885e8 SH |
113 | // This is a system level page that operates on other contexts. |
114 | require_login(); | |
115 | ||
484c4c6c SH |
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); | |
121 | } | |
122 | redirect($url); | |
123 | } | |
124 | ||
b488058f SH |
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' | |
134 | ); | |
135 | if ($category && !has_any_capability($capabilities, $systemcontext)) { | |
484c4c6c SH |
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. | |
b488058f | 138 | // This will at least give the page some relevant navigation. |
b488058f | 139 | navigation_node::override_active_url(new moodle_url('/course/management.php', array('categoryid' => $category->id))); |
484c4c6c SH |
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()); | |
143 | } | |
144 | if (!$issearching && $category !== null) { | |
5dc361e1 | 145 | $parents = coursecat::get_many($category->get_parents()); |
484c4c6c | 146 | $parents[] = $category; |
5dc361e1 | 147 | foreach ($parents as $parent) { |
484c4c6c SH |
148 | $PAGE->navbar->add( |
149 | $parent->get_formatted_name(), | |
150 | new moodle_url('/course/management.php', array('categoryid' => $parent->id)) | |
151 | ); | |
5dc361e1 | 152 | } |
5dc361e1 SH |
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()); | |
156 | } | |
157 | } | |
158 | ||
5dc361e1 SH |
159 | $notificationspass = array(); |
160 | $notificationsfail = array(); | |
161 | ||
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 | switch ($action) { | |
181 | case 'resortcategories' : | |
182 | $sort = required_param('resort', PARAM_ALPHA); | |
b488058f SH |
183 | $cattosort = coursecat::get((int)optional_param('categoryid', 0, PARAM_INT)); |
184 | $redirectback = \core_course\management\helper::action_category_resort_subcategories($cattosort, $sort); | |
5dc361e1 SH |
185 | break; |
186 | case 'resortcourses' : | |
187 | // They must have specified a category. | |
188 | required_param('categoryid', PARAM_INT); | |
189 | $sort = required_param('resort', PARAM_ALPHA); | |
190 | \core_course\management\helper::action_category_resort_courses($category, $sort); | |
191 | break; | |
192 | case 'showcourse' : | |
193 | $redirectback = \core_course\management\helper::action_course_show($course); | |
194 | break; | |
195 | case 'hidecourse' : | |
196 | $redirectback = \core_course\management\helper::action_course_hide($course); | |
197 | break; | |
198 | case 'movecourseup' : | |
199 | // They must have specified a category and a course. | |
200 | required_param('categoryid', PARAM_INT); | |
201 | required_param('courseid', PARAM_INT); | |
5aff38e4 | 202 | $redirectback = \core_course\management\helper::action_course_change_sortorder_up_one($course, $category); |
5dc361e1 SH |
203 | break; |
204 | case 'movecoursedown' : | |
205 | // They must have specified a category and a course. | |
206 | required_param('categoryid', PARAM_INT); | |
207 | required_param('courseid', PARAM_INT); | |
5aff38e4 | 208 | $redirectback = \core_course\management\helper::action_course_change_sortorder_down_one($course, $category); |
5dc361e1 SH |
209 | break; |
210 | case 'showcategory' : | |
211 | // They must have specified a category. | |
212 | required_param('categoryid', PARAM_INT); | |
213 | $redirectback = \core_course\management\helper::action_category_show($category); | |
214 | break; | |
215 | case 'hidecategory' : | |
216 | // They must have specified a category. | |
217 | required_param('categoryid', PARAM_INT); | |
218 | $redirectback = \core_course\management\helper::action_category_hide($category); | |
219 | break; | |
220 | case 'movecategoryup' : | |
221 | // They must have specified a category. | |
222 | required_param('categoryid', PARAM_INT); | |
5aff38e4 | 223 | $redirectback = \core_course\management\helper::action_category_change_sortorder_up_one($category); |
5dc361e1 SH |
224 | break; |
225 | case 'movecategorydown' : | |
226 | // They must have specified a category. | |
227 | required_param('categoryid', PARAM_INT); | |
5aff38e4 | 228 | $redirectback = \core_course\management\helper::action_category_change_sortorder_down_one($category); |
5dc361e1 SH |
229 | break; |
230 | case 'deletecategory': | |
231 | // They must have specified a category. | |
232 | required_param('categoryid', PARAM_INT); | |
233 | if (!$category->can_delete()) { | |
234 | throw new moodle_exception('permissiondenied', 'error', '', null, 'coursecat::can_resort'); | |
235 | } | |
236 | require_once($CFG->dirroot.'/course/delete_category_form.php'); | |
67e1f268 | 237 | $mform = new core_course_deletecategory_form(null, $category); |
5dc361e1 | 238 | if ($mform->is_cancelled()) { |
484c4c6c | 239 | redirect($PAGE->url); |
5dc361e1 SH |
240 | } |
241 | // Start output. | |
242 | /* @var core_course_management_renderer|core_renderer $renderer */ | |
243 | $renderer = $PAGE->get_renderer('core_course', 'management'); | |
244 | echo $renderer->header(); | |
245 | echo $renderer->heading(get_string('deletecategory', 'moodle', $category->get_formatted_name())); | |
246 | ||
247 | if ($data = $mform->get_data()) { | |
248 | // The form has been submit handle it. | |
249 | if ($data->fulldelete == 1 && $category->can_delete_full()) { | |
8aa3aa3d SH |
250 | $continueurl = new moodle_url('/course/management.php'); |
251 | if ($category->parent != '0') { | |
252 | $continueurl->param('categoryid', $category->parent); | |
253 | } | |
5dc361e1 SH |
254 | $notification = get_string('coursecategorydeleted', '', $category->get_formatted_name()); |
255 | $deletedcourses = $category->delete_full(true); | |
256 | foreach ($deletedcourses as $course) { | |
257 | echo $renderer->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess'); | |
258 | } | |
259 | echo $renderer->notification($notification, 'notifysuccess'); | |
260 | echo $renderer->continue_button($continueurl); | |
261 | } else if ($data->fulldelete == 0 && $category->can_move_content_to($data->newparent)) { | |
262 | $continueurl = new moodle_url('/course/management.php', array('categoryid' => $data->newparent)); | |
263 | $category->delete_move($data->newparent, true); | |
264 | echo $renderer->continue_button($continueurl); | |
265 | } else { | |
266 | // Some error in parameters (user is cheating?) | |
267 | $mform->display(); | |
268 | } | |
269 | } else { | |
270 | // Display the form. | |
271 | $mform->display(); | |
272 | } | |
273 | // Finish output and exit. | |
274 | echo $renderer->footer(); | |
275 | exit(); | |
276 | break; | |
277 | case 'bulkaction': | |
278 | $bulkmovecourses = optional_param('bulkmovecourses', false, PARAM_BOOL); | |
279 | $bulkmovecategories = optional_param('bulkmovecategories', false, PARAM_BOOL); | |
280 | $bulkresortcategories = optional_param('bulkresortcategories', false, PARAM_BOOL); | |
281 | ||
282 | if ($bulkmovecourses) { | |
283 | // Move courses out of the current category and into a new category. | |
284 | // They must have specified a category. | |
285 | required_param('categoryid', PARAM_INT); | |
286 | $movetoid = required_param('movecoursesto', PARAM_INT); | |
287 | $courseids = optional_param_array('bc', false, PARAM_INT); | |
288 | if ($courseids === false) { | |
289 | break; | |
290 | } | |
291 | $moveto = coursecat::get($movetoid); | |
67e1f268 SH |
292 | try { |
293 | // If this fails we want to catch the exception and report it. | |
294 | $redirectback = \core_course\management\helper::action_category_move_courses_into($category, $moveto, | |
295 | $courseids); | |
296 | } catch (moodle_exception $ex) { | |
297 | $redirectback = false; | |
298 | $notificationsfail[] = $ex->getMessage(); | |
299 | } | |
5dc361e1 | 300 | } else if ($bulkmovecategories) { |
d61c05ac | 301 | $categoryids = optional_param_array('bcat', array(), PARAM_INT); |
5dc361e1 SH |
302 | $movetocatid = required_param('movecategoriesto', PARAM_INT); |
303 | $movetocat = coursecat::get($movetocatid); | |
304 | $movecount = 0; | |
305 | foreach ($categoryids as $id) { | |
306 | $cattomove = coursecat::get($id); | |
307 | if ($id == $movetocatid) { | |
308 | $notificationsfail[] = get_string('movecategoryownparent', 'error', $cattomove->get_formatted_name()); | |
309 | continue; | |
310 | } | |
311 | if (strpos($movetocat->path, $cattomove->path) === 0) { | |
312 | $notificationsfail[] = get_string('movecategoryparentconflict', 'error', $cattomove->get_formatted_name()); | |
313 | continue; | |
314 | } | |
315 | if ($cattomove->parent != $movetocatid) { | |
316 | if ($cattomove->can_change_parent($movetocatid)) { | |
317 | $cattomove->change_parent($movetocatid); | |
318 | $movecount++; | |
319 | } else { | |
320 | $notificationsfail[] = get_string('movecategorynotpossible', 'error', $cattomove->get_formatted_name()); | |
321 | } | |
322 | } | |
323 | } | |
324 | if ($movecount > 1) { | |
325 | $a = new stdClass; | |
326 | $a->count = $movecount; | |
327 | $a->to = $movetocat->get_formatted_name(); | |
3b732cd6 RT |
328 | $movesuccessstrkey = 'movecategoriessuccess'; |
329 | if ($movetocatid == 0) { | |
330 | $movesuccessstrkey = 'movecategoriestotopsuccess'; | |
331 | } | |
332 | $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a); | |
5dc361e1 SH |
333 | } else if ($movecount === 1) { |
334 | $a = new stdClass; | |
335 | $a->moved = $cattomove->get_formatted_name(); | |
336 | $a->to = $movetocat->get_formatted_name(); | |
3b732cd6 RT |
337 | $movesuccessstrkey = 'movecategorysuccess'; |
338 | if ($movetocatid == 0) { | |
339 | $movesuccessstrkey = 'movecategorytotopsuccess'; | |
340 | } | |
341 | $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a); | |
5dc361e1 SH |
342 | } |
343 | } else if ($bulkresortcategories) { | |
344 | // Bulk resort selected categories. | |
345 | $categoryids = optional_param_array('bcat', false, PARAM_INT); | |
346 | $sort = required_param('resortcategoriesby', PARAM_ALPHA); | |
347 | if ($categoryids === false) { | |
348 | break; | |
349 | } | |
350 | $categories = coursecat::get_many($categoryids); | |
351 | foreach ($categories as $cat) { | |
352 | // Don't clean up here, we'll do it once we're all done. | |
353 | \core_course\management\helper::action_category_resort_subcategories($cat, $sort, false); | |
354 | } | |
355 | coursecat::resort_categories_cleanup(); | |
b488058f SH |
356 | if ($category === null && count($categoryids) === 1) { |
357 | // They're bulk sorting just a single category and they've not selected a category. | |
358 | // Lets for convenience sake auto-select the category that has been resorted for them. | |
359 | redirect(new moodle_url($PAGE->url, array('categoryid' => reset($categoryids)))); | |
360 | } | |
5dc361e1 SH |
361 | } |
362 | } | |
363 | if ($redirectback) { | |
364 | redirect($PAGE->url); | |
365 | } | |
366 | } | |
367 | ||
368 | if (!is_null($perpage)) { | |
369 | set_user_preference('coursecat_management_perpage', $perpage); | |
370 | } else { | |
371 | $perpage = get_user_preferences('coursecat_management_perpage', $CFG->coursesperpage); | |
372 | } | |
373 | if ((int)$perpage != $perpage || $perpage < 2) { | |
374 | $perpage = $CFG->coursesperpage; | |
375 | } | |
376 | ||
377 | $categorysize = 4; | |
378 | $coursesize = 4; | |
379 | $detailssize = 4; | |
380 | if ($viewmode === 'default' || $viewmode === 'combined') { | |
381 | if (isset($courseid)) { | |
382 | $class = 'columns-3'; | |
383 | } else { | |
384 | $categorysize = 5; | |
385 | $coursesize = 7; | |
386 | $class = 'columns-2'; | |
387 | } | |
388 | } else if ($viewmode === 'categories') { | |
389 | $categorysize = 12; | |
390 | $class = 'columns-1'; | |
391 | } else if ($viewmode === 'courses') { | |
392 | if (isset($courseid)) { | |
393 | $coursesize = 6; | |
394 | $detailssize = 6; | |
395 | $class = 'columns-2'; | |
396 | } else { | |
397 | $coursesize = 12; | |
398 | $class = 'columns-1'; | |
399 | } | |
400 | } | |
484c4c6c SH |
401 | if ($viewmode === 'default' || $viewmode === 'combined') { |
402 | $class .= ' viewmode-cobmined'; | |
403 | } else { | |
404 | $class .= ' viewmode-'.$viewmode; | |
405 | } | |
406 | if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) { | |
407 | $class .= ' course-selected'; | |
408 | } | |
5dc361e1 SH |
409 | |
410 | /* @var core_course_management_renderer|core_renderer $renderer */ | |
411 | $renderer = $PAGE->get_renderer('core_course', 'management'); | |
412 | $renderer->enhance_management_interface(); | |
413 | ||
414 | echo $renderer->header(); | |
415 | ||
416 | if (!$issearching) { | |
62846237 | 417 | echo $renderer->management_heading($strmanagement, $viewmode, $categoryid); |
5dc361e1 SH |
418 | } else { |
419 | echo $renderer->management_heading(new lang_string('searchresults')); | |
420 | } | |
421 | ||
422 | if (count($notificationspass) > 0) { | |
423 | echo $renderer->notification(join('<br />', $notificationspass), 'notifysuccess'); | |
424 | } | |
425 | if (count($notificationsfail) > 0) { | |
426 | echo $renderer->notification(join('<br />', $notificationsfail)); | |
427 | } | |
428 | ||
429 | // Start the management form. | |
430 | echo $renderer->management_form_start(); | |
431 | ||
432 | echo $renderer->grid_start('course-category-listings', $class); | |
433 | if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'categories') { | |
434 | echo $renderer->grid_column_start($categorysize, 'category-listing'); | |
435 | echo $renderer->category_listing($category); | |
436 | echo $renderer->grid_column_end(); | |
437 | } | |
438 | if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') { | |
439 | echo $renderer->grid_column_start($coursesize, 'course-listing'); | |
440 | if (!$issearching) { | |
441 | echo $renderer->course_listing($category, $course, $page, $perpage); | |
442 | } else { | |
443 | list($courses, $coursescount, $coursestotal) = | |
444 | \core_course\management\helper::search_courses($search, $blocklist, $modulelist, $page, $perpage); | |
445 | echo $renderer->search_listing($courses, $coursestotal, $course, $page, $perpage); | |
446 | } | |
447 | echo $renderer->grid_column_end(); | |
448 | if (isset($courseid)) { | |
449 | echo $renderer->grid_column_start($detailssize, 'course-detail'); | |
450 | echo $renderer->course_detail($course); | |
451 | echo $renderer->grid_column_end(); | |
452 | } | |
453 | } | |
454 | echo $renderer->grid_end(); | |
455 | ||
5dc361e1 SH |
456 | // End of the management form. |
457 | echo $renderer->management_form_end(); | |
d61c05ac | 458 | echo $renderer->footer(); |