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