MDL-42629 course: set a better management page title
[moodle.git] / course / management.php
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/>.
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  */
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';
44 }
46 $issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== '');
47 if ($issearching) {
48     $viewmode = 'courses';
49 }
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';
78     }
79     $context = $systemcontext;
80     navigation_node::override_active_url($url);
81 }
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 }
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);
104 $strmanagement = new lang_string('coursecatmanagement');
105 $title = 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($title);
111 $PAGE->set_heading($strmanagement);
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);
121     }
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'
134 );
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))
151         );
152     }
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     }
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     switch ($action) {
181         case 'resortcategories' :
182             $sort = required_param('resort', PARAM_ALPHA);
183             $cattosort = coursecat::get((int)optional_param('categoryid', 0, PARAM_INT));
184             $redirectback = \core_course\management\helper::action_category_resort_subcategories($cattosort, $sort);
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);
202             $redirectback = \core_course\management\helper::action_course_change_sortorder_up_one($course, $category);
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);
208             $redirectback = \core_course\management\helper::action_course_change_sortorder_down_one($course, $category);
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);
223             $redirectback = \core_course\management\helper::action_category_change_sortorder_up_one($category);
224             break;
225         case 'movecategorydown' :
226             // They must have specified a category.
227             required_param('categoryid', PARAM_INT);
228             $redirectback = \core_course\management\helper::action_category_change_sortorder_down_one($category);
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');
237             $mform = new core_course_deletecategory_form(null, $category);
238             if ($mform->is_cancelled()) {
239                 redirect($PAGE->url);
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()));
247             if ($data = $mform->get_data()) {
248                 // The form has been submit handle it.
249                 if ($data->fulldelete == 1 && $category->can_delete_full()) {
250                     $continueurl = new moodle_url('/course/management.php');
251                     if ($category->parent != '0') {
252                         $continueurl->param('categoryid', $category->parent);
253                     }
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);
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);
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                 }
300             } else if ($bulkmovecategories) {
301                 $categoryids = optional_param_array('bcat', array(), PARAM_INT);
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();
328                     $movesuccessstrkey = 'movecategoriessuccess';
329                     if ($movetocatid == 0) {
330                         $movesuccessstrkey = 'movecategoriestotopsuccess';
331                     }
332                     $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
333                 } else if ($movecount === 1) {
334                     $a = new stdClass;
335                     $a->moved = $cattomove->get_formatted_name();
336                     $a->to = $movetocat->get_formatted_name();
337                     $movesuccessstrkey = 'movecategorysuccess';
338                     if ($movetocatid == 0) {
339                         $movesuccessstrkey = 'movecategorytotopsuccess';
340                     }
341                     $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
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();
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                 }
361             }
362     }
363     if ($redirectback) {
364         redirect($PAGE->url);
365     }
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);
373 if ((int)$perpage != $perpage || $perpage < 2) {
374     $perpage = $CFG->coursesperpage;
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     }
401 if ($viewmode === 'default' || $viewmode === 'combined') {
402     $class .= ' viewmode-cobmined';
403 } else {
404     $class .= ' viewmode-'.$viewmode;
406 if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) {
407     $class .= ' course-selected';
410 /* @var core_course_management_renderer|core_renderer $renderer */
411 $renderer = $PAGE->get_renderer('core_course', 'management');
412 $renderer->enhance_management_interface();
414 echo $renderer->header();
416 if (!$issearching) {
417     echo $renderer->management_heading($strmanagement, $viewmode, $categoryid);
418 } else {
419     echo $renderer->management_heading(new lang_string('searchresults'));
422 if (count($notificationspass) > 0) {
423     echo $renderer->notification(join('<br />', $notificationspass), 'notifysuccess');
425 if (count($notificationsfail) > 0) {
426     echo $renderer->notification(join('<br />', $notificationsfail));
429 // Start the management form.
430 echo $renderer->management_form_start();
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();
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     }
454 echo $renderer->grid_end();
456 // End of the management form.
457 echo $renderer->management_form_end();
458 echo $renderer->footer();