Merge branch 'wip-MDL-38147-master' of git://github.com/marinaglancy/moodle

Conflicts:
	lib/upgrade.txt
This commit is contained in:
Damyon Wiese 2013-03-26 14:22:11 +08:00
commit 0c241b980a
31 changed files with 3323 additions and 1007 deletions

View File

@ -1,6 +1,7 @@
<?php
include_once($CFG->dirroot . '/course/lib.php');
include_once($CFG->libdir . '/coursecatlib.php');
class block_course_list extends block_list {
function init() {
@ -53,11 +54,11 @@ class block_course_list extends block_list {
}
}
$categories = get_categories("0"); // Parent = 0 ie top-level categories only
$categories = coursecat::get(0)->get_children(); // Parent = 0 ie top-level categories only
if ($categories) { //Check we have categories
if (count($categories) > 1 || (count($categories) == 1 && $DB->count_records('course') > 200)) { // Just print top level category links
foreach ($categories as $category) {
$categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
$categoryname = $category->get_formatted_name();
$linkcss = $category->visible ? "" : " class=\"dimmed\" ";
$this->content->items[]="<a $linkcss href=\"$CFG->wwwroot/course/category.php?id=$category->id\">".$icon . $categoryname . "</a>";
}

View File

@ -85,9 +85,9 @@ class cohort_edit_form extends moodleform {
}
protected function get_category_options($currentcontextid) {
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist, 'moodle/cohort:manage');
global $CFG;
require_once($CFG->libdir. '/coursecatlib.php');
$displaylist = coursecat::make_categories_list('moodle/cohort:manage');
$options = array();
$syscontext = context_system::instance();
if (has_capability('moodle/cohort:manage', $syscontext)) {

View File

@ -102,7 +102,7 @@ function cohort_delete_cohort($cohort) {
* Somehow deal with cohorts when deleting course category,
* we can not just delete them because they might be used in enrol
* plugins or referenced in external systems.
* @param stdClass $category
* @param stdClass|coursecat $category
* @return void
*/
function cohort_delete_category($category) {

View File

@ -26,6 +26,7 @@
require_once("../config.php");
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/textlib.class.php');
require_once($CFG->libdir. '/coursecatlib.php');
$id = required_param('id', PARAM_INT); // Category id
$page = optional_param('page', 0, PARAM_INT); // which page to show
@ -69,9 +70,7 @@ $PAGE->set_button(print_course_search('', true, 'navbar'));
echo $OUTPUT->header();
/// Print the category selector
$displaylist = array();
$notused = array();
make_categories_list($displaylist, $notused);
$displaylist = coursecat::make_categories_list();
echo '<div class="categorypicker">';
$select = new single_select(new moodle_url('/course/category.php'), 'id', $displaylist, $category->id, null, 'switchcategory');

View File

@ -97,9 +97,8 @@ class course_completion_form extends moodleform {
}
// Get category list
$list = array();
$parents = array();
make_categories_list($list, $parents);
require_once($CFG->libdir. '/coursecatlib.php');
$list = coursecat::make_categories_list();
// Get course list for select box
$selectbox = array();

View File

@ -6,69 +6,25 @@ if (!defined('MOODLE_INTERNAL')) {
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->libdir. '/coursecatlib.php');
class delete_category_form extends moodleform {
var $_category;
function definition() {
global $CFG, $DB;
$mform = & $this->_form;
$this->_category = $this->_customdata;
$categorycontext = context_coursecat::instance($this->_category->id);
$mform =& $this->_form;
$category = $this->_customdata;
$categorycontext = context_coursecat::instance($category->id);
$this->_category = $category;
// Check permissions, to see if it OK to give the option to delete
// the contents, rather than move elsewhere.
$candeletecontent = $this->_category->can_delete_full();
/// Check permissions, to see if it OK to give the option to delete
/// the contents, rather than move elsewhere.
/// Are there any subcategories of this one, can they be deleted?
$candeletecontent = true;
$tocheck = get_child_categories($category->id);
$containscategories = !empty($tocheck);
$categoryids = array($category->id);
while (!empty($tocheck)) {
$checkcat = array_pop($tocheck);
$childcategoryids[] = $checkcat->id;
$tocheck = $tocheck + get_child_categories($checkcat->id);
$chcontext = context_coursecat::instance($checkcat->id);
if ($candeletecontent && !has_capability('moodle/category:manage', $chcontext)) {
$candeletecontent = false;
}
}
// Get the list of categories we might be able to move to.
$displaylist = $this->_category->move_content_targets_list();
/// Are there any courses in here, can they be deleted?
list($test, $params) = $DB->get_in_or_equal($categoryids);
$containedcourses = $DB->get_records_sql(
"SELECT id,1 FROM {course} c WHERE c.category $test", $params);
$containscourses = false;
if ($containedcourses) {
$containscourses = true;
foreach ($containedcourses as $courseid => $notused) {
if ($candeletecontent && !can_delete_course($courseid)) {
$candeletecontent = false;
break;
}
}
}
/// Are there any questions in the question bank here?
$containsquestions = question_context_has_any_questions($categorycontext);
/// Get the list of categories we might be able to move to.
$testcaps = array();
if ($containscourses) {
$testcaps[] = 'moodle/course:create';
}
if ($containscategories || $containsquestions) {
$testcaps[] = 'moodle/category:manage';
}
$displaylist = array();
$notused = array();
if (!empty($testcaps)) {
make_categories_list($displaylist, $notused, $testcaps, $category->id);
}
/// Now build the options.
// Now build the options.
$options = array();
if ($displaylist) {
$options[0] = get_string('movecontentstoanothercategory');
@ -76,60 +32,57 @@ class delete_category_form extends moodleform {
if ($candeletecontent) {
$options[1] = get_string('deleteallcannotundo');
}
if (empty($options)) {
print_error('youcannotdeletecategory', 'error', 'index.php', $this->_category->get_formatted_name());
}
/// Now build the form.
$mform->addElement('header','general', get_string('categorycurrentcontents', '', format_string($category->name, true, array('context' => $categorycontext))));
// Now build the form.
$mform->addElement('header','general', get_string('categorycurrentcontents', '', $this->_category->get_formatted_name()));
if ($containscourses || $containscategories || $containsquestions) {
if (empty($options)) {
print_error('youcannotdeletecategory', 'error', 'index.php', format_string($category->name, true, array('context' => $categorycontext)));
}
/// Describe the contents of this category.
$contents = '<ul>';
if ($containscategories) {
$contents .= '<li>' . get_string('subcategories') . '</li>';
}
if ($containscourses) {
$contents .= '<li>' . get_string('courses') . '</li>';
}
if ($containsquestions) {
$contents .= '<li>' . get_string('questionsinthequestionbank') . '</li>';
}
$contents .= '</ul>';
$mform->addElement('static', 'emptymessage', get_string('thiscategorycontains'), $contents);
/// Give the options for what to do.
$mform->addElement('select', 'fulldelete', get_string('whattodo'), $options);
if (count($options) == 1) {
$optionkeys = array_keys($options);
$option = reset($optionkeys);
$mform->hardFreeze('fulldelete');
$mform->setConstant('fulldelete', $option);
}
if ($displaylist) {
$mform->addElement('select', 'newparent', get_string('movecategorycontentto'), $displaylist);
if (in_array($category->parent, $displaylist)) {
$mform->setDefault('newparent', $category->parent);
}
$mform->disabledIf('newparent', 'fulldelete', 'eq', '1');
}
// Describe the contents of this category.
$contents = '';
if ($this->_category->has_children()) {
$contents .= '<li>' . get_string('subcategories') . '</li>';
}
if ($this->_category->has_courses()) {
$contents .= '<li>' . get_string('courses') . '</li>';
}
if (question_context_has_any_questions($categorycontext)) {
$contents .= '<li>' . get_string('questionsinthequestionbank') . '</li>';
}
if (!empty($contents)) {
$mform->addElement('static', 'emptymessage', get_string('thiscategorycontains'), html_writer::tag('ul', $contents));
} else {
$mform->addElement('hidden', 'fulldelete', 1);
$mform->setType('fulldelete', PARAM_INT);
$mform->addElement('static', 'emptymessage', '', get_string('deletecategoryempty'));
}
// Give the options for what to do.
$mform->addElement('select', 'fulldelete', get_string('whattodo'), $options);
if (count($options) == 1) {
$optionkeys = array_keys($options);
$option = reset($optionkeys);
$mform->hardFreeze('fulldelete');
$mform->setConstant('fulldelete', $option);
}
if ($displaylist) {
$mform->addElement('select', 'newparent', get_string('movecategorycontentto'), $displaylist);
if (in_array($this->_category->parent, $displaylist)) {
$mform->setDefault('newparent', $this->_category->parent);
}
$mform->disabledIf('newparent', 'fulldelete', 'eq', '1');
}
$mform->addElement('hidden', 'deletecat');
$mform->setType('deletecat', PARAM_ALPHANUM);
$mform->addElement('hidden', 'sure');
$mform->setType('sure', PARAM_ALPHANUM);
$mform->setDefault('sure', md5(serialize($category)));
$mform->setDefault('sure', md5(serialize($this->_category)));
//--------------------------------------------------------------------------------
$this->add_action_buttons(true, get_string('delete'));
$this->set_data(array('deletecat' => $this->_category->id));
}
/// perform some extra moodle validation

View File

@ -4,6 +4,7 @@ defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir. '/coursecatlib.php');
class course_edit_form extends moodleform {
protected $course;
@ -48,9 +49,7 @@ class course_edit_form extends moodleform {
// verify permissions to change course category or keep current
if (empty($course->id)) {
if (has_capability('moodle/course:create', $categorycontext)) {
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist, 'moodle/course:create');
$displaylist = coursecat::make_categories_list('moodle/course:create');
$mform->addElement('select', 'category', get_string('category'), $displaylist);
$mform->addHelpButton('category', 'category');
$mform->setDefault('category', $category->id);
@ -61,12 +60,10 @@ class course_edit_form extends moodleform {
}
} else {
if (has_capability('moodle/course:changecategory', $coursecontext)) {
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist, 'moodle/course:create');
$displaylist = coursecat::make_categories_list('moodle/course:create');
if (!isset($displaylist[$course->category])) {
//always keep current
$displaylist[$course->category] = format_string($DB->get_field('course_categories', 'name', array('id'=>$course->category)));
$displaylist[$course->category] = coursecat::get($course->category)->get_formatted_name();
}
$mform->addElement('select', 'category', get_string('category'), $displaylist);
$mform->addHelpButton('category', 'category');

View File

@ -27,8 +27,9 @@
*/
require_once('../config.php');
require_once('lib.php');
require_once('editcategory_form.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->dirroot.'/course/editcategory_form.php');
require_once($CFG->libdir.'/coursecatlib.php');
require_login();
@ -92,42 +93,16 @@ if ($mform->is_cancelled()) {
redirect($CFG->wwwroot .'/course/manage.php');
}
} else if ($data = $mform->get_data()) {
$newcategory = new stdClass();
$newcategory->name = $data->name;
$newcategory->idnumber = $data->idnumber;
$newcategory->description_editor = $data->description_editor;
$newcategory->parent = $data->parent; // if $data->parent = 0, the new category will be a top-level category
if (isset($data->theme) && !empty($CFG->allowcategorythemes)) {
$newcategory->theme = $data->theme;
}
$logaction = 'update';
if ($id) {
// Update an existing category.
$newcategory->id = $category->id;
if ($newcategory->parent != $category->parent) {
// check category manage capability if parent changed
require_capability('moodle/category:manage', get_category_or_system_context((int)$newcategory->parent));
$parent_cat = $DB->get_record('course_categories', array('id' => $newcategory->parent));
move_category($newcategory, $parent_cat);
$newcategory = coursecat::get($id);
if ($data->parent != $category->parent && !$newcategory->can_change_parent($data->parent)) {
print_error('cannotmovecategory');
}
$newcategory->update($data, $editoroptions);
} else {
// Create a new category.
$newcategory->description = $data->description_editor['text'];
// Don't overwrite the $newcategory object as it'll be processed by file_postupdate_standard_editor in a moment
$category = create_course_category($newcategory);
$newcategory->id = $category->id;
$categorycontext = $category->context;
$logaction = 'add';
$newcategory = coursecat::create($data, $editoroptions);
}
$newcategory = file_postupdate_standard_editor($newcategory, 'description', $editoroptions, $categorycontext, 'coursecat', 'description', 0);
$DB->update_record('course_categories', $newcategory);
add_to_log(SITEID, "category", $logaction, "editcategory.php?id=$newcategory->id", $newcategory->id);
fix_course_sortorder();
redirect('manage.php?id='.$newcategory->id);
}

View File

@ -4,6 +4,7 @@ if (!defined('MOODLE_INTERNAL')) {
}
require_once ($CFG->dirroot.'/course/moodleform_mod.php');
require_once ($CFG->libdir.'/coursecatlib.php');
class editcategory_form extends moodleform {
// form definition
@ -18,17 +19,16 @@ class editcategory_form extends moodleform {
if (has_capability('moodle/category:manage', get_system_context()) || $category->parent == 0) {
$options[0] = get_string('top');
}
$parents = array();
if ($category->id) {
// Editing an existing category.
make_categories_list($options, $parents, 'moodle/category:manage', $category->id);
$options += coursecat::make_categories_list('moodle/category:manage', $category->id);
if (empty($options[$category->parent])) {
$options[$category->parent] = $DB->get_field('course_categories', 'name', array('id'=>$category->parent));
}
$strsubmit = get_string('savechanges');
} else {
// Making a new category
make_categories_list($options, $parents, 'moodle/category:manage');
$options += coursecat::make_categories_list('moodle/category:manage');
$strsubmit = get_string('createcategory');
}

View File

@ -1650,7 +1650,7 @@ class core_course_external extends external_api {
*/
public static function create_categories($categories) {
global $CFG, $DB;
require_once($CFG->dirroot . "/course/lib.php");
require_once($CFG->libdir . "/coursecatlib.php");
$params = self::validate_parameters(self::create_categories_parameters(),
array('categories' => $categories));
@ -1670,38 +1670,10 @@ class core_course_external extends external_api {
self::validate_context($context);
require_capability('moodle/category:manage', $context);
// Check name.
if (textlib::strlen($category['name'])>255) {
throw new moodle_exception('categorytoolong');
}
// this will validate format and throw an exception if there are errors
external_validate_format($category['descriptionformat']);
$newcategory = new stdClass();
$newcategory->name = $category['name'];
$newcategory->parent = $category['parent'];
// Format the description.
if (!empty($category['description'])) {
$newcategory->description = $category['description'];
}
$newcategory->descriptionformat = external_validate_format($category['descriptionformat']);
if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
$newcategory->theme = $category['theme'];
}
// Check id number.
if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
if (textlib::strlen($category['idnumber'])>100) {
throw new moodle_exception('idnumbertoolong');
}
if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
if ($existing->id) {
throw new moodle_exception('idnumbertaken');
}
}
$newcategory->idnumber = $category['idnumber'];
}
$newcategory = create_course_category($newcategory);
// Populate special fields.
fix_course_sortorder();
$newcategory = coursecat::create($category);
$createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
}
@ -1764,7 +1736,7 @@ class core_course_external extends external_api {
*/
public static function update_categories($categories) {
global $CFG, $DB;
require_once($CFG->dirroot . "/course/lib.php");
require_once($CFG->libdir . "/coursecatlib.php");
// Validate parameters.
$params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
@ -1772,49 +1744,16 @@ class core_course_external extends external_api {
$transaction = $DB->start_delegated_transaction();
foreach ($params['categories'] as $cat) {
if (!$category = $DB->get_record('course_categories', array('id' => $cat['id']))) {
throw new moodle_exception('unknowcategory');
}
$category = coursecat::get($cat['id']);
$categorycontext = context_coursecat::instance($cat['id']);
self::validate_context($categorycontext);
require_capability('moodle/category:manage', $categorycontext);
if (!empty($cat['name'])) {
if (textlib::strlen($cat['name'])>255) {
throw new moodle_exception('categorytoolong');
}
$category->name = $cat['name'];
}
if (!empty($cat['idnumber'])) {
if (textlib::strlen($cat['idnumber'])>100) {
throw new moodle_exception('idnumbertoolong');
}
$category->idnumber = $cat['idnumber'];
}
if (!empty($cat['description'])) {
$category->description = $cat['description'];
$category->descriptionformat = external_validate_format($cat['descriptionformat']);
}
if (!empty($cat['theme'])) {
$category->theme = $cat['theme'];
}
if (!empty($cat['parent']) && ($category->parent != $cat['parent'])) {
// First check if parent exists.
if (!$parent_cat = $DB->get_record('course_categories', array('id' => $cat['parent']))) {
throw new moodle_exception('unknowcategory');
}
// Then check if we have capability.
self::validate_context(get_category_or_system_context((int)$cat['parent']));
require_capability('moodle/category:manage', get_category_or_system_context((int)$cat['parent']));
// Finally move the category.
move_category($category, $parent_cat);
$category->parent = $cat['parent'];
// Get updated path by move_category().
$category->path = $DB->get_field('course_categories', 'path',
array('id' => $category->id));
}
$DB->update_record('course_categories', $category);
// this will throw an exception if descriptionformat is not valid
external_validate_format($cat['descriptionformat']);
$category->update($cat);
}
$transaction->allow_commit();
@ -1864,6 +1803,7 @@ class core_course_external extends external_api {
public static function delete_categories($categories) {
global $CFG, $DB;
require_once($CFG->dirroot . "/course/lib.php");
require_once($CFG->libdir . "/coursecatlib.php");
// Validate parameters.
$params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
@ -1871,9 +1811,7 @@ class core_course_external extends external_api {
$transaction = $DB->start_delegated_transaction();
foreach ($params['categories'] as $category) {
if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
throw new moodle_exception('unknowcategory');
}
$deletecat = coursecat::get($category['id'], MUST_EXIST);
$context = context_coursecat::instance($deletecat->id);
require_capability('moodle/category:manage', $context);
self::validate_context($context);
@ -1881,29 +1819,32 @@ class core_course_external extends external_api {
if ($category['recursive']) {
// If recursive was specified, then we recursively delete the category's contents.
category_delete_full($deletecat, false);
if ($deletecat->can_delete_full()) {
$deletecat->delete_full(false);
} else {
throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
}
} else {
// In this situation, we don't delete the category's contents, we either move it to newparent or parent.
// If the parent is the root, moving is not supported (because a course must always be inside a category).
// We must move to an existing category.
if (!empty($category['newparent'])) {
if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
throw new moodle_exception('unknowcategory');
}
$newparent = $category['newparent'];
$newparentcat = coursecat::get($category['newparent']);
} else {
$newparent = $deletecat->parent;
$newparentcat = coursecat::get($deletecat->parent);
}
// This operation is not allowed. We must move contents to an existing category.
if ($newparent == 0) {
if (!$newparentcat->id) {
throw new moodle_exception('movecatcontentstoroot');
}
$parentcontext = get_category_or_system_context($newparent);
require_capability('moodle/category:manage', $parentcontext);
self::validate_context($parentcontext);
category_delete_move($deletecat, $newparent, false);
self::validate_context(context_coursecat::instance($newparentcat->id));
if ($deletecat->can_move_content_to($newparentcat->id)) {
$deletecat->delete_move($newparentcat->id, false);
} else {
throw new moodle_exception('youcannotdeletecategory', '', '', $deletecat->get_formatted_name());
}
}
}

View File

@ -1219,129 +1219,6 @@ function get_category_or_system_context($categoryid) {
}
}
/**
* Gets the child categories of a given courses category. Uses a static cache
* to make repeat calls efficient.
*
* @param int $parentid the id of a course category.
* @return array all the child course categories.
*/
function get_child_categories($parentid) {
static $allcategories = null;
// only fill in this variable the first time
if (null == $allcategories) {
$allcategories = array();
$categories = get_categories();
foreach ($categories as $category) {
if (empty($allcategories[$category->parent])) {
$allcategories[$category->parent] = array();
}
$allcategories[$category->parent][] = $category;
}
}
if (empty($allcategories[$parentid])) {
return array();
} else {
return $allcategories[$parentid];
}
}
/**
* This function recursively travels the categories, building up a nice list
* for display. It also makes an array that list all the parents for each
* category.
*
* For example, if you have a tree of categories like:
* Miscellaneous (id = 1)
* Subcategory (id = 2)
* Sub-subcategory (id = 4)
* Other category (id = 3)
* Then after calling this function you will have
* $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
* 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
* 3 => 'Other category');
* $parents = array(2 => array(1), 4 => array(1, 2));
*
* If you specify $requiredcapability, then only categories where the current
* user has that capability will be added to $list, although all categories
* will still be added to $parents, and if you only have $requiredcapability
* in a child category, not the parent, then the child catgegory will still be
* included.
*
* If you specify the option $excluded, then that category, and all its children,
* are omitted from the tree. This is useful when you are doing something like
* moving categories, where you do not want to allow people to move a category
* to be the child of itself.
*
* @param array $list For output, accumulates an array categoryid => full category path name
* @param array $parents For output, accumulates an array categoryid => list of parent category ids.
* @param string/array $requiredcapability if given, only categories where the current
* user has this capability will be added to $list. Can also be an array of capabilities,
* in which case they are all required.
* @param integer $excludeid Omit this category and its children from the lists built.
* @param object $category Build the tree starting at this category - otherwise starts at the top level.
* @param string $path For internal use, as part of recursive calls.
*/
function make_categories_list(&$list, &$parents, $requiredcapability = '',
$excludeid = 0, $category = NULL, $path = "") {
// initialize the arrays if needed
if (!is_array($list)) {
$list = array();
}
if (!is_array($parents)) {
$parents = array();
}
if (empty($category)) {
// Start at the top level.
$category = new stdClass;
$category->id = 0;
} else {
// This is the excluded category, don't include it.
if ($excludeid > 0 && $excludeid == $category->id) {
return;
}
$context = context_coursecat::instance($category->id);
$categoryname = format_string($category->name, true, array('context' => $context));
// Update $path.
if ($path) {
$path = $path.' / '.$categoryname;
} else {
$path = $categoryname;
}
// Add this category to $list, if the permissions check out.
if (empty($requiredcapability)) {
$list[$category->id] = $path;
} else {
$requiredcapability = (array)$requiredcapability;
if (has_all_capabilities($requiredcapability, $context)) {
$list[$category->id] = $path;
}
}
}
// Add all the children recursively, while updating the parents array.
if ($categories = get_child_categories($category->id)) {
foreach ($categories as $cat) {
if (!empty($category->id)) {
if (isset($parents[$category->id])) {
$parents[$cat->id] = $parents[$category->id];
}
$parents[$cat->id][] = $category->id;
}
make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
}
}
}
/**
* This function generates a structured array of courses and categories.
*
@ -1358,14 +1235,14 @@ function make_categories_list(&$list, &$parents, $requiredcapability = '',
*/
function get_course_category_tree($id = 0, $depth = 0) {
global $DB, $CFG;
$viewhiddencats = has_capability('moodle/category:viewhiddencategories', context_system::instance());
$categories = get_child_categories($id);
require_once($CFG->libdir. '/coursecatlib.php');
if (!$coursecat = coursecat::get($id, IGNORE_MISSING)) {
return array();
}
$categories = array();
$categoryids = array();
foreach ($categories as $key => &$category) {
if (!$category->visible && !$viewhiddencats) {
unset($categories[$key]);
continue;
}
foreach ($coursecat->get_children() as $child) {
$categories[] = $category = (object)convert_to_array($child);
$categoryids[$category->id] = $category;
if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
@ -1417,37 +1294,31 @@ function get_course_category_tree($id = 0, $depth = 0) {
*/
function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true, $categorycourses=NULL) {
global $CFG;
require_once($CFG->libdir. '/coursecatlib.php');
// maxcategorydepth == 0 meant no limit
if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
return;
}
if (!$displaylist) {
make_categories_list($displaylist, $parentslist);
// make sure category is visible to the current user
if ($category) {
if (!$coursecat = coursecat::get($category->id, IGNORE_MISSING)) {
return;
}
} else {
$coursecat = coursecat::get(0);
}
if (!$categorycourses) {
if ($category) {
$categorycourses = get_category_courses_array($category->id);
} else {
$categorycourses = get_category_courses_array();
}
$categorycourses = get_category_courses_array($coursecat->id);
}
if ($category) {
if ($category->visible or has_capability('moodle/category:viewhiddencategories', context_system::instance())) {
print_category_info($category, $depth, $showcourses, $categorycourses[$category->id]);
} else {
return; // Don't bother printing children of invisible categories
}
} else {
$category = new stdClass();
$category->id = "0";
if ($coursecat->id) {
print_category_info($category, $depth, $showcourses, $categorycourses[$category->id]);
}
if ($categories = get_child_categories($category->id)) { // Print all the children recursively
if ($categories = $coursecat->get_children()) { // Print all the children recursively
$countcats = count($categories);
$count = 0;
$first = true;
@ -1497,18 +1368,19 @@ function get_category_courses_array_recursively(array &$flattened, $category) {
}
/**
* This function will return $options array for html_writer::select(), with whitespace to denote nesting.
* Returns full course categories trees to be used in html_writer::select()
*
* Calls {@link coursecat::make_categories_list()} to build the tree and
* adds whitespace to denote nesting
*
* @return array array mapping coursecat id to the display name
*/
function make_categories_options() {
make_categories_list($cats,$parents);
global $CFG;
require_once($CFG->libdir. '/coursecatlib.php');
$cats = coursecat::make_categories_list();
foreach ($cats as $key => $value) {
if (array_key_exists($key,$parents)) {
if ($indent = count($parents[$key])) {
for ($i = 0; $i < $indent; $i++) {
$cats[$key] = '&nbsp;'.$cats[$key];
}
}
}
$cats[$key] = str_repeat('&nbsp;', coursecat::get($key)->depth - 1). $value;
}
return $cats;
}
@ -1672,9 +1544,10 @@ function can_edit_in_category($categoryid = 0) {
*/
function print_courses($category) {
global $CFG, $OUTPUT;
require_once($CFG->libdir. '/coursecatlib.php');
if (!is_object($category) && $category==0) {
$categories = get_child_categories(0); // Parent = 0 ie top-level categories only
$categories = coursecat::get(0)->get_children(); // Parent = 0 ie top-level categories only
if (is_array($categories) && count($categories) == 1) {
$category = array_shift($categories);
$courses = get_courses_wmanagers($category->id,
@ -2732,112 +2605,6 @@ function course_allowed_module($course, $modname) {
return has_capability($capability, $coursecontext);
}
/**
* Recursively delete category including all subcategories and courses.
* @param stdClass $category
* @param boolean $showfeedback display some notices
* @return array return deleted courses
*/
function category_delete_full($category, $showfeedback=true) {
global $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
foreach ($children as $childcat) {
category_delete_full($childcat, $showfeedback);
}
}
$deletedcourses = array();
if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
foreach ($courses as $course) {
if (!delete_course($course, false)) {
throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
}
$deletedcourses[] = $course;
}
}
// move or delete cohorts in this context
cohort_delete_category($category);
// now delete anything that may depend on course category context
grade_course_category_delete($category->id, 0, $showfeedback);
if (!question_delete_course_category($category, 0, $showfeedback)) {
throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
}
// finally delete the category and it's context
$DB->delete_records('course_categories', array('id'=>$category->id));
delete_context(CONTEXT_COURSECAT, $category->id);
add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
events_trigger('course_category_deleted', $category);
return $deletedcourses;
}
/**
* Delete category, but move contents to another category.
* @param object $ccategory
* @param int $newparentid category id
* @return bool status
*/
function category_delete_move($category, $newparentid, $showfeedback=true) {
global $CFG, $DB, $OUTPUT;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
return false;
}
if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
foreach ($children as $childcat) {
move_category($childcat, $newparentcat);
}
}
if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
if (!move_courses(array_keys($courses), $newparentid)) {
if ($showfeedback) {
echo $OUTPUT->notification("Error moving courses");
}
return false;
}
if ($showfeedback) {
echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
}
}
// move or delete cohorts in this context
cohort_delete_category($category);
// now delete anything that may depend on course category context
grade_course_category_delete($category->id, $newparentid, $showfeedback);
if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
if ($showfeedback) {
echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
}
return false;
}
// finally delete the category and it's context
$DB->delete_records('course_categories', array('id'=>$category->id));
delete_context(CONTEXT_COURSECAT, $category->id);
add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
events_trigger('course_category_deleted', $category);
if ($showfeedback) {
echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
}
return true;
}
/**
* Efficiently moves many courses around while maintaining
* sortorder in order.
@ -2882,98 +2649,11 @@ function move_courses($courseids, $categoryid) {
}
}
fix_course_sortorder();
cache_helper::purge_by_event('changesincourse');
return true;
}
/**
* Hide course category and child course and subcategories
* @param stdClass $category
* @return void
*/
function course_category_hide($category) {
global $DB;
$category->visible = 0;
$DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
$DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
$DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($category->id)); // store visible flag so that we can return to it if we immediately unhide
$DB->set_field('course', 'visible', 0, array('category' => $category->id));
// get all child categories and hide too
if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
foreach ($subcats as $cat) {
$DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
$DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
$DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
$DB->set_field('course', 'visible', 0, array('category' => $cat->id));
}
}
add_to_log(SITEID, "category", "hide", "editcategory.php?id=$category->id", $category->id);
}
/**
* Show course category and child course and subcategories
* @param stdClass $category
* @return void
*/
function course_category_show($category) {
global $DB;
$category->visible = 1;
$DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
$DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
$DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
// get all child categories and unhide too
if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
foreach ($subcats as $cat) {
if ($cat->visibleold) {
$DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
}
$DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
}
}
add_to_log(SITEID, "category", "show", "editcategory.php?id=$category->id", $category->id);
}
/**
* Efficiently moves a category - NOTE that this can have
* a huge impact access-control-wise...
*/
function move_category($category, $newparentcat) {
global $CFG, $DB;
$context = context_coursecat::instance($category->id);
$hidecat = false;
if (empty($newparentcat->id)) {
$DB->set_field('course_categories', 'parent', 0, array('id' => $category->id));
$newparent = context_system::instance();
} else {
$DB->set_field('course_categories', 'parent', $newparentcat->id, array('id' => $category->id));
$newparent = context_coursecat::instance($newparentcat->id);
if (!$newparentcat->visible and $category->visible) {
// better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
$hidecat = true;
}
}
context_moved($context, $newparent);
// now make it last in new category
$DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
// Log action.
add_to_log(SITEID, "category", "move", "editcategory.php?id=$category->id", $category->id);
// and fix the sortorders
fix_course_sortorder();
if ($hidecat) {
course_category_hide($category);
}
}
/**
* Returns the display name of the given section that the course prefers
*
@ -3149,6 +2829,8 @@ function create_course($data, $editoroptions = NULL) {
course_create_sections_if_missing($course, 0);
fix_course_sortorder();
// purge appropriate caches in case fix_course_sortorder() did not change anything
cache_helper::purge_by_event('changesincourse');
// new context created - better mark it as dirty
mark_context_dirty($context->path);
@ -3167,32 +2849,6 @@ function create_course($data, $editoroptions = NULL) {
return $course;
}
/**
* Create a new course category and marks the context as dirty
*
* This function does not set the sortorder for the new category and
* @see{fix_course_sortorder} should be called after creating a new course
* category
*
* Please note that this function does not verify access control.
*
* @param object $category All of the data required for an entry in the course_categories table
* @return object new course category
*/
function create_course_category($category) {
global $DB;
$category->timemodified = time();
$category->id = $DB->insert_record('course_categories', $category);
$category = $DB->get_record('course_categories', array('id' => $category->id));
// We should mark the context as dirty
$category->context = context_coursecat::instance($category->id);
$category->context->mark_dirty();
return $category;
}
/**
* Update a course.
*
@ -3255,6 +2911,8 @@ function update_course($data, $editoroptions = NULL) {
}
fix_course_sortorder();
// purge appropriate caches in case fix_course_sortorder() did not change anything
cache_helper::purge_by_event('changesincourse');
// Test for and remove blocks which aren't appropriate anymore
blocks_remove_inappropriate($course);
@ -3555,6 +3213,31 @@ class course_request {
return $this->properties->collision;
}
/**
* Returns the category where this course request should be created
*
* Note that we don't check here that user has a capability to view
* hidden categories if he has capabilities 'moodle/site:approvecourse' and
* 'moodle/course:changecategory'
*
* @return coursecat
*/
public function get_category() {
global $CFG;
require_once($CFG->libdir.'/coursecatlib.php');
// If the category is not set, if the current user does not have the rights to change the category, or if the
// category does not exist, we set the default category to the course to be approved.
// The system level is used because the capability moodle/site:approvecourse is based on a system level.
if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
(!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) {
$category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true);
}
if (!$category) {
$category = coursecat::get_default();
}
return $category;
}
/**
* This function approves the request turning it into a course
*
@ -3577,18 +3260,9 @@ class course_request {
unset($data->reason);
unset($data->requester);
// If the category is not set, if the current user does not have the rights to change the category, or if the
// category does not exist, we set the default category to the course to be approved.
// The system level is used because the capability moodle/site:approvecourse is based on a system level.
if (empty($data->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
(!$category = get_course_category($data->category))) {
$category = get_course_category($CFG->defaultrequestcategory);
}
// Set category
$category = $this->get_category();
$data->category = $category->id;
$data->sortorder = $category->sortorder; // place as the first in category
// Set misc settings
$data->requested = 1;

View File

@ -24,6 +24,7 @@
require_once("../config.php");
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/coursecatlib.php');
// Category id.
$id = optional_param('id', 0, PARAM_INT);
@ -51,18 +52,17 @@ $movedowncat = optional_param('movedowncat', 0, PARAM_INT);
require_login();
// Retrieve coursecat object
// This will also make sure that category is accessible and create default category if missing
$coursecat = coursecat::get($id);
if ($id) {
$PAGE->set_category_by_id($id);
$PAGE->set_url(new moodle_url('/course/manage.php', array('id' => $id)));
// This is sure to be the category context.
$context = $PAGE->context;
// And the object has been loaded for us no need for another DB call.
$category = $PAGE->category;
if (!can_edit_in_category($category->id)) {
redirect(new moodle_url('/course/category.php', array('id' => $category->id)));
}
if (!$category->visible) {
require_capability('moodle/category:viewhiddencategories', $context);
if (!can_edit_in_category($coursecat->id)) {
redirect(new moodle_url('/course/category.php', array('id' => $coursecat->id)));
}
} else {
$context = context_system::instance();
@ -75,26 +75,10 @@ if ($id) {
$canmanage = has_capability('moodle/category:manage', $context);
// Check the default category exists.
if (!$id && !$DB->record_exists('course_categories', array('parent' => 0))) {
// No category yet! Try and make one.
$tempcat = new stdClass;
$tempcat->name = get_string('miscellaneous');
$tempcat->id = $DB->insert_record('course_categories', $tempcat);
// Fetch the context to ensure it is created.
context_coursecat::instance($tempcat->id);
mark_context_dirty('/'.SYSCONTEXTID);
// Required to build course_categories.depth and categories.path.
fix_course_sortorder();
set_config('defaultrequestcategory', $tempcat->id);
// Unset the temp category. We no longer need it.
unset($tempcat);
}
// Process any category actions.
if (!empty($deletecat) and confirm_sesskey()) {
// Delete a category.
$cattodelete = $DB->get_record('course_categories', array('id' => $deletecat), '*', MUST_EXIST);
$cattodelete = coursecat::get($deletecat);
$context = context_coursecat::instance($deletecat);
require_capability('moodle/category:manage', $context);
require_capability('moodle/category:manage', get_category_or_system_context($cattodelete->parent));
@ -103,7 +87,6 @@ if (!empty($deletecat) and confirm_sesskey()) {
require_once($CFG->dirroot.'/course/delete_category_form.php');
$mform = new delete_category_form(null, $cattodelete);
$mform->set_data(array('deletecat' => $deletecat));
if ($mform->is_cancelled()) {
redirect(new moodle_url('/course/manage.php'));
}
@ -114,25 +97,24 @@ if (!empty($deletecat) and confirm_sesskey()) {
if ($data = $mform->get_data()) {
// The form has been submit handle it.
if ($data->fulldelete) {
$deletedcourses = category_delete_full($cattodelete, true);
if ($data->fulldelete == 1 && $cattodelete->can_delete_full()) {
$cattodeletename = $cattodelete->get_formatted_name();
$deletedcourses = $cattodelete->delete_full(true);
foreach ($deletedcourses as $course) {
echo $OUTPUT->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess');
}
$cattodeletename = format_string($cattodelete->name, true, array('context' => $context));
echo $OUTPUT->notification(get_string('coursecategorydeleted', '', $cattodeletename), 'notifysuccess');
echo $OUTPUT->continue_button(new moodle_url('/course/manage.php'));
} else if ($data->fulldelete == 0 && $cattodelete->can_move_content_to($data->newparent)) {
$cattodelete->delete_move($data->newparent, true);
echo $OUTPUT->continue_button(new moodle_url('/course/manage.php'));
} else {
category_delete_move($cattodelete, $data->newparent, true);
// Some error in parameters (user is cheating?)
$mform->display();
}
if ($deletecat == $CFG->defaultrequestcategory) {
// If we deleted $CFG->defaultrequestcategory, make it point somewhere else.
set_config('defaultrequestcategory', $DB->get_field('course_categories', 'MIN(id)', array('parent' => 0)));
}
echo $OUTPUT->continue_button(new moodle_url('/course/manage.php'));
} else {
// Display the form.
require_once($CFG->libdir . '/questionlib.php');
$mform->display();
}
// Finish output and exit.
@ -142,31 +124,25 @@ if (!empty($deletecat) and confirm_sesskey()) {
if (!empty($movecat) and ($movetocat >= 0) and confirm_sesskey()) {
// Move a category to a new parent if required.
if ($cattomove = $DB->get_record('course_categories', array('id' => $movecat))) {
require_capability('moodle/category:manage', get_category_or_system_context($cattomove->parent));
if ($cattomove->parent != $movetocat) {
$newparent = $DB->get_record('course_categories', array('id' => $movetocat));
require_capability('moodle/category:manage', get_category_or_system_context($movetocat));
move_category($cattomove, $newparent);
$cattomove = coursecat::get($movecat);
if ($cattomove->parent != $movetocat) {
if ($cattomove->can_change_parent($movetocat)) {
$cattomove->change_parent($movetocat);
} else {
print_error('cannotmovecategory');
}
}
}
// Hide or show a category.
if ($hidecat and confirm_sesskey()) {
if ($tempcat = $DB->get_record('course_categories', array('id' => $hidecat))) {
require_capability('moodle/category:manage', get_category_or_system_context($tempcat->parent));
if ($tempcat->visible == 1) {
course_category_hide($tempcat);
}
}
$cattohide = coursecat::get($hidecat);
require_capability('moodle/category:manage', get_category_or_system_context($cattohide->parent));
$cattohide->hide();
} else if ($showcat and confirm_sesskey()) {
if ($tempcat = $DB->get_record('course_categories', array('id' => $showcat))) {
require_capability('moodle/category:manage', get_category_or_system_context($tempcat->parent));
if ($tempcat->visible == 0) {
course_category_show($tempcat);
}
}
$cattoshow = coursecat::get($showcat);
require_capability('moodle/category:manage', get_category_or_system_context($cattoshow->parent));
$cattoshow->show();
}
if ((!empty($moveupcat) or !empty($movedowncat)) and confirm_sesskey()) {
@ -201,17 +177,18 @@ if ((!empty($moveupcat) or !empty($movedowncat)) and confirm_sesskey()) {
fix_course_sortorder();
}
if (isset($category) && $canmanage && $resort && confirm_sesskey()) {
if ($coursecat->id && $canmanage && $resort && confirm_sesskey()) {
// Resort the category.
if ($courses = get_courses($category->id, '', 'c.id,c.fullname,c.sortorder')) {
if ($courses = get_courses($coursecat->id, '', 'c.id,c.fullname,c.sortorder')) {
collatorlib::asort_objects_by_property($courses, 'fullname', collatorlib::SORT_NATURAL);
$i = 1;
foreach ($courses as $course) {
$DB->set_field('course', 'sortorder', $category->sortorder + $i, array('id' => $course->id));
$DB->set_field('course', 'sortorder', $coursecat->sortorder + $i, array('id' => $course->id));
$i++;
}
// This should not be needed but we do it just to be safe.
fix_course_sortorder();
cache_helper::purge_by_event('changesincourse');
}
}
@ -257,6 +234,7 @@ if ((!empty($hide) or !empty($show)) && confirm_sesskey()) {
// Set the visibility of the course. we set the old flag when user manually changes visibility of course.
$params = array('id' => $course->id, 'visible' => $visible, 'visibleold' => $visible, 'timemodified' => time());
$DB->update_record('course', $params);
cache_helper::purge_by_event('changesincourse');
add_to_log($course->id, "course", ($visible ? 'show' : 'hide'), "edit.php?id=$course->id", $course->id);
}
@ -284,6 +262,7 @@ if ((!empty($moveup) or !empty($movedown)) && confirm_sesskey()) {
}
$DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id));
$DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id));
cache_helper::purge_by_event('changesincourse');
add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id);
}
}
@ -314,24 +293,18 @@ if (can_edit_in_category()) {
$PAGE->navbar->add($settingsnode->text, $settingsnode->action);
}
} else {
// If we get here then they must have arrived here using a specific category
// within which they can manage.
// We can safetly assume $category is set.
$site = get_site();
$PAGE->set_title("$site->shortname: $category->name");
$PAGE->set_title("$site->shortname: $coursecat->name");
$PAGE->set_heading($site->fullname);
$PAGE->set_button(print_course_search('', true, 'navbar'));
}
$parentlist = array();
$displaylist = array();
make_categories_list($displaylist, $parentlist);
$displaylist[0] = get_string('top');
// Start output.
echo $OUTPUT->header();
if (!isset($category)) {
if (!$coursecat->id) {
// Print out the categories with all the knobs.
$table = new html_table;
$table->id = 'coursecategories';
@ -350,12 +323,12 @@ if (!isset($category)) {
);
$table->data = array();
print_category_edit($table, null, $displaylist, $parentlist);
print_category_edit($table, $coursecat);
echo html_writer::table($table);
} else {
// Print the category selector.
$select = new single_select(new moodle_url('/course/manage.php'), 'id', $displaylist, $category->id, null, 'switchcategory');
$select = new single_select(new moodle_url('/course/manage.php'), 'id', $displaylist, $coursecat->id, null, 'switchcategory');
$select->set_label(get_string('categories').':');
echo html_writer::start_tag('div', array('class' => 'categorypicker'));
@ -382,7 +355,7 @@ if ($canmanage) {
echo $OUTPUT->container_end();
}
if (isset($category)) {
if ($coursecat->id) {
// Print out all the sub-categories (plain mode).
// In order to view hidden subcategories the user must have the viewhiddencategories.
// capability in the current category..
@ -401,7 +374,7 @@ if (isset($category)) {
ctx.contextlevel = :contextlevel
$categorywhere
ORDER BY cc.sortorder ASC";
$subcategories = $DB->get_recordset_sql($sql, array('parentid' => $category->id, 'contextlevel' => CONTEXT_COURSECAT));
$subcategories = $DB->get_recordset_sql($sql, array('parentid' => $coursecat->id, 'contextlevel' => CONTEXT_COURSECAT));
// Prepare a table to display the sub categories.
$table = new html_table;
$table->attributes = array(
@ -430,7 +403,7 @@ if (isset($category)) {
echo html_writer::table($table);
}
$courses = get_courses_page($category->id, 'c.sortorder ASC',
$courses = get_courses_page($coursecat->id, 'c.sortorder ASC',
'c.id,c.sortorder,c.shortname,c.fullname,c.summary,c.visible',
$totalcount, $page*$perpage, $perpage);
$numcourses = count($courses);
@ -552,9 +525,7 @@ if (!$courses) {
}
if ($abletomovecourses) {
$movetocategories = array();
$notused = array();
make_categories_list($movetocategories, $notused, 'moodle/category:manage');
$movetocategories = coursecat::make_categories_list('moodle/category:manage');
$movetocategories[$id] = get_string('moveselectedcoursesto');
$cell = new html_table_cell();
@ -593,8 +564,8 @@ if ($canmanage and $numcourses > 1) {
if (has_capability('moodle/course:create', $context)) {
// Print button to create a new course.
$url = new moodle_url('/course/edit.php');
if (isset($category)) {
$url->params(array('category' => $category->id, 'returnto' => 'catmanage'));
if ($coursecat->id) {
$url->params(array('category' => $coursecat->id, 'returnto' => 'catmanage'));
} else {
$url->params(array('category' => $CFG->defaultrequestcategory, 'returnto' => 'topcatmanage'));
}
@ -614,14 +585,12 @@ echo $OUTPUT->footer();
* Recursive function to print all the categories ready for editing.
*
* @param html_table $table The table to add data to.
* @param stdClass $category The category to render
* @param array $displaylist The categories this can be moved to.
* @param array $parentslist An array of categories.
* @param coursecat $category The category to render
* @param int $depth The depth of the category.
* @param bool $up True if this category can be moved up.
* @param bool $down True if this category can be moved down.
*/
function print_category_edit(html_table $table, $category, $displaylist, $parentslist, $depth=-1, $up=false, $down=false) {
function print_category_edit(html_table $table, coursecat $category, $depth = -1, $up = false, $down = false) {
global $OUTPUT;
static $str = null;
@ -639,22 +608,20 @@ function print_category_edit(html_table $table, $category, $displaylist, $parent
$str->spacer = $OUTPUT->spacer().' ';
}
if (!empty($category)) {
if ($category->id) {
if (!isset($category->context)) {
$category->context = context_coursecat::instance($category->id);
}
$categorycontext = context_coursecat::instance($category->id);
$attributes = array();
$attributes['class'] = $category->visible ? '' : 'dimmed';
$attributes['title'] = $str->edit;
$categoryurl = new moodle_url('/course/manage.php', array('id' => $category->id, 'sesskey' => sesskey()));
$categoryname = format_string($category->name, true, array('context' => $category->context));
$categoryname = $category->get_formatted_name();
$categorypadding = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;', $depth);
$categoryname = $categorypadding . html_writer::link($categoryurl, $categoryname, $attributes);
$icons = array();
if (has_capability('moodle/category:manage', $category->context)) {
if (has_capability('moodle/category:manage', $categorycontext)) {
// Edit category.
$icons[] = $OUTPUT->action_icon(
new moodle_url('/course/editcategory.php', array('id' => $category->id)),
@ -682,9 +649,9 @@ function print_category_edit(html_table $table, $category, $displaylist, $parent
);
}
// Cohorts.
if (has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), $category->context)) {
if (has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), $categorycontext)) {
$icons[] = $OUTPUT->action_icon(
new moodle_url('/cohort/index.php', array('contextid' => $category->context->id)),
new moodle_url('/cohort/index.php', array('contextid' => $categorycontext->id)),
new pix_icon('t/cohort', $str->cohorts, 'moodle', array('class' => 'iconsmall')),
null, array('title' => $str->cohorts)
);
@ -711,15 +678,9 @@ function print_category_edit(html_table $table, $category, $displaylist, $parent
}
$actions = '';
if (has_capability('moodle/category:manage', $category->context)) {
$tempdisplaylist = $displaylist;
unset($tempdisplaylist[$category->id]);
foreach ($parentslist as $key => $parents) {
if (in_array($category->id, $parents)) {
unset($tempdisplaylist[$key]);
}
}
if (has_capability('moodle/category:manage', $categorycontext)) {
$popupurl = new moodle_url("manage.php?movecat=$category->id&sesskey=".sesskey());
$tempdisplaylist = array(0 => get_string('top')) + coursecat::make_categories_list('moodle/category:manage', $category->id);
$select = new single_select($popupurl, 'movetocat', $tempdisplaylist, $category->parent, null, "moveform$category->id");
$select->set_label(get_string('frontpagecategorynames'), array('class' => 'accesshide'));
$actions = $OUTPUT->render($select);
@ -735,14 +696,9 @@ function print_category_edit(html_table $table, $category, $displaylist, $parent
// Actions.
new html_table_cell($actions)
));
// Get the subcategories to be printed.
$categories = get_categories($category->id);
} else {
$categories = get_categories(0);
}
if ($categories) {
if ($categories = $category->get_children()) {
// Print all the children recursively.
$countcats = count($categories);
$count = 0;
@ -757,7 +713,7 @@ function print_category_edit(html_table $table, $category, $displaylist, $parent
$down = $last ? false : true;
$first = false;
print_category_edit($table, $cat, $displaylist, $parentslist, $depth+1, $up, $down);
print_category_edit($table, $cat, $depth+1, $up, $down);
}
}
}

View File

@ -110,23 +110,14 @@ if (empty($pending)) {
// Check here for shortname collisions and warn about them.
$course->check_shortname_collision();
// Retreiving category name.
// If the category was not set (can happen after upgrade) or if the user does not have the capability
// to change the category, we fallback on the default one.
// Else, the category proposed is fetched, but we fallback on the default one if we can't find it.
// It is just a matter of displaying the right information because the logic when approving the category
// proceeds the same way. The system context level is used as moodle/site:approvecourse uses it.
if (empty($course->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
(!$category = get_course_category($course->category))) {
$category = get_course_category($CFG->defaultrequestcategory);
}
$category = $course->get_category();
$row = array();
$row[] = format_string($course->shortname);
$row[] = format_string($course->fullname);
$row[] = fullname($course->get_requester());
$row[] = $course->summary;
$row[] = format_string($category->name);
$row[] = $category->get_formatted_name();
$row[] = format_string($course->reason);
$row[] = $OUTPUT->single_button(new moodle_url($baseurl, array('approve' => $course->id, 'sesskey' => sesskey())), get_string('approve'), 'get') .
$OUTPUT->single_button(new moodle_url($baseurl, array('reject' => $course->id)), get_string('rejectdots'), 'get');

View File

@ -36,6 +36,7 @@ if (!defined('MOODLE_INTERNAL')) {
}
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/coursecatlib.php');
/**
* A form for a user to request a course.
@ -69,9 +70,7 @@ class course_request_form extends moodleform {
$mform->setType('shortname', PARAM_TEXT);
if (!empty($CFG->requestcategoryselection)) {
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist, '');
$displaylist = coursecat::make_categories_list();
$mform->addElement('select', 'category', get_string('category'), $displaylist);
$mform->setDefault('category', $CFG->defaultrequestcategory);
$mform->addHelpButton('category', 'category');

View File

@ -24,6 +24,7 @@
require_once("../config.php");
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/coursecatlib.php');
$search = optional_param('search', '', PARAM_RAW); // search words
$page = optional_param('page', 0, PARAM_INT); // which page to show
@ -38,14 +39,8 @@ $modulelist= optional_param('modulelist', '', PARAM_PLUGIN);
// List of minimum capabilities which user need to have for editing/moving course
$capabilities = array('moodle/course:create', 'moodle/category:manage');
// List of category id's in which current user has course:create and category:manage capability.
$usercatlist = array();
// List of parent category id's
$catparentlist = array();
// Populate usercatlist with list of category id's with required capabilities.
make_categories_list($usercatlist, $catparentlist, $capabilities);
// Populate usercatlist with list of category id's with course:create and category:manage capabilities.
$usercatlist = coursecat::make_categories_list($capabilities);
$search = trim(strip_tags($search)); // trim & clean raw searched string
if ($search) {
@ -109,9 +104,7 @@ if (has_capability('moodle/course:visibility', context_system::instance())) {
}
}
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist);
$displaylist = coursecat::make_categories_list();
$strcourses = new lang_string("courses");
$strsearch = new lang_string("search");

View File

@ -785,54 +785,6 @@ class courselib_testcase extends advanced_testcase {
$this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
}
public function test_create_course_category() {
global $CFG, $DB;
$this->resetAfterTest(true);
// Create the category
$data = new stdClass();
$data->name = 'aaa';
$data->description = 'aaa';
$data->idnumber = '';
$category1 = create_course_category($data);
// Initially confirm that base data was inserted correctly
$this->assertEquals($data->name, $category1->name);
$this->assertEquals($data->description, $category1->description);
$this->assertEquals($data->idnumber, $category1->idnumber);
// sortorder should be blank initially
$this->assertEmpty($category1->sortorder);
// Calling fix_course_sortorder() should provide a new sortorder
fix_course_sortorder();
$category1 = $DB->get_record('course_categories', array('id' => $category1->id));
$this->assertGreaterThanOrEqual(1, $category1->sortorder);
// Create two more categories and test the sortorder worked correctly
$data->name = 'ccc';
$category2 = create_course_category($data);
$this->assertEmpty($category2->sortorder);
$data->name = 'bbb';
$category3 = create_course_category($data);
$this->assertEmpty($category3->sortorder);
// Calling fix_course_sortorder() should provide a new sortorder to give category1,
// category2, category3. New course categories are ordered by id not name
fix_course_sortorder();
$category1 = $DB->get_record('course_categories', array('id' => $category1->id));
$category2 = $DB->get_record('course_categories', array('id' => $category2->id));
$category3 = $DB->get_record('course_categories', array('id' => $category3->id));
$this->assertGreaterThanOrEqual($category1->sortorder, $category2->sortorder);
$this->assertGreaterThanOrEqual($category2->sortorder, $category3->sortorder);
$this->assertGreaterThanOrEqual($category1->sortorder, $category3->sortorder);
}
public function test_move_module_in_course() {
global $DB;

View File

@ -108,12 +108,7 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_configtext('enrol_database/newcoursecategory', get_string('newcoursecategory', 'enrol_database'), '', ''));
if (!during_initial_install()) {
require_once($CFG->dirroot.'/course/lib.php');
$options = array();
$parentlist = array();
make_categories_list($options, $parentlist);
$settings->add(new admin_setting_configselect('enrol_database/defaultcategory', get_string('defaultcategory', 'enrol_database'), get_string('defaultcategory_desc', 'enrol_database'), 1, $options));
unset($parentlist);
$settings->add(new admin_setting_configselect('enrol_database/defaultcategory', get_string('defaultcategory', 'enrol_database'), get_string('defaultcategory_desc', 'enrol_database'), 1, make_categories_options()));
}
$settings->add(new admin_setting_configtext('enrol_database/templatecourse', get_string('templatecourse', 'enrol_database'), get_string('templatecourse_desc', 'enrol_database'), ''));

View File

@ -94,10 +94,7 @@ if ($ADMIN->fulltree) {
$options = $yesno;
$settings->add(new admin_setting_configselect('enrol_ldap/autocreate', get_string('autocreate_key', 'enrol_ldap'), get_string('autocreate', 'enrol_ldap'), 0, $options));
if (!during_initial_install()) {
require_once($CFG->dirroot.'/course/lib.php');
$parentlist = array();
$options = array();
make_categories_list($options, $parentlist);
$options = make_categories_options();
$settings->add(new admin_setting_configselect('enrol_ldap/category', get_string('category_key', 'enrol_ldap'), get_string('category', 'enrol_ldap'), key($options), $options));
}
$settings->add(new admin_setting_configtext_trim_lower('enrol_ldap/template', get_string('template_key', 'enrol_ldap'), get_string('template', 'enrol_ldap'), ''));

View File

@ -36,6 +36,9 @@ $string['cacheadmin'] = 'Cache administration';
$string['cacheconfig'] = 'Configuration';
$string['cachedef_calendar_subscriptions'] = 'Calendar subscriptions';
$string['cachedef_config'] = 'Config settings';
$string['cachedef_coursecat'] = 'Course categories lists for particular user';
$string['cachedef_coursecatrecords'] = 'Course categories records';
$string['cachedef_coursecattree'] = 'Course categories tree';
$string['cachedef_databasemeta'] = 'Database meta information';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
$string['cachedef_groupdata'] = 'Course group information';

View File

@ -105,6 +105,7 @@ $string['cannotmarktopic'] = 'Could not mark that topic for this course';
$string['cannotmigratedatacomments'] = 'Cannot migrate data module comments';
$string['cannotmodulename'] = 'Cannot get the module name in build navigation';
$string['cannotmoduletype'] = 'Cannot get the module type in build navigation';
$string['cannotmovecategory'] = 'Cannot move category';
$string['cannotmoverolewithid'] = 'Cannot move role with ID {$a}';
$string['cannotopencsv'] = 'Cannot open CSV file';
$string['cannotopenfile'] = 'Cannot open file ({$a})';
@ -159,7 +160,9 @@ $string['cannotviewprofile'] = 'You cannot view the profile of this user';
$string['cannotviewreport'] = 'You cannot view this report';
$string['cannotwritefile'] = 'Cannot write to file ({$a})';
$string['categoryerror'] = 'Category error';
$string['categorynamerequired'] = 'Category name is required';
$string['categorytoolong'] = 'Category name too long';
$string['categoryidnumbertaken'] = 'ID number is already used for another category';
$string['commentmisconf'] = 'Comment ID is misconfigured';
$string['componentisuptodate'] = 'Component is up-to-date';
$string['confirmsesskeybad'] = 'Sorry, but your session key could not be confirmed to carry out this action. This security feature prevents against accidental or malicious execution of important functions in your name. Please make sure you really wanted to execute this function.';

2121
lib/coursecatlib.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -837,7 +837,7 @@ function get_courses_wmanagers($categoryid=0, $sort="c.sortorder ASC", $fields=a
* @param int $totalcount Passed in by reference.
* @return object {@link $COURSE} records
*/
function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $recordsperpage=50, &$totalcount) {
function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$totalcount) {
global $CFG, $DB;
if ($DB->sql_regex_supported()) {
@ -906,7 +906,8 @@ function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $record
$limitto = $limitfrom + $recordsperpage;
list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
$sql = "SELECT c.* $ccselect
$fields = array_diff(array_keys($DB->get_columns('course')), array('modinfo', 'sectioncache'));
$sql = "SELECT c.".join(',c.',$fields)." $ccselect
FROM {course} c
$ccjoin
WHERE $searchcond AND c.id <> ".SITEID."
@ -914,17 +915,21 @@ function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $record
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $course) {
context_instance_preload($course);
$coursecontext = context_course::instance($course->id);
if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
// Don't exit this loop till the end
// we need to count all the visible courses
// to update $totalcount
if ($c >= $limitfrom && $c < $limitto) {
$courses[$course->id] = $course;
if (!$course->visible) {
// preload contexts only for hidden courses or courses we need to return
context_instance_preload($course);
$coursecontext = context_course::instance($course->id);
if (!has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
continue;
}
$c++;
}
// Don't exit this loop till the end
// we need to count all the visible courses
// to update $totalcount
if ($c >= $limitfrom && $c < $limitto) {
$courses[$course->id] = $course;
}
$c++;
}
$rs->close();
@ -934,140 +939,6 @@ function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $record
return $courses;
}
/**
* Returns a sorted list of categories. Each category object has a context
* property that is a context object.
*
* When asking for $parent='none' it will return all the categories, regardless
* of depth. Wheen asking for a specific parent, the default is to return
* a "shallow" resultset. Pass false to $shallow and it will return all
* the child categories as well.
*
* @global object
* @uses CONTEXT_COURSECAT
* @param string $parent The parent category if any
* @param string $sort the sortorder
* @param bool $shallow - set to false to get the children too
* @return array of categories
*/
function get_categories($parent='none', $sort=NULL, $shallow=true) {
global $DB;
if ($sort === NULL) {
$sort = 'ORDER BY cc.sortorder ASC';
} elseif ($sort ==='') {
// leave it as empty
} else {
$sort = "ORDER BY $sort";
}
list($ccselect, $ccjoin) = context_instance_preload_sql('cc.id', CONTEXT_COURSECAT, 'ctx');
if ($parent === 'none') {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
$sort";
$params = array();
} elseif ($shallow) {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
WHERE cc.parent=?
$sort";
$params = array($parent);
} else {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
JOIN {course_categories} ccp
ON ((cc.parent = ccp.id) OR (cc.path LIKE ".$DB->sql_concat('ccp.path',"'/%'")."))
WHERE ccp.id=?
$sort";
$params = array($parent);
}
$categories = array();
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $cat) {
context_instance_preload($cat);
$catcontext = context_coursecat::instance($cat->id);
if ($cat->visible || has_capability('moodle/category:viewhiddencategories', $catcontext)) {
$categories[$cat->id] = $cat;
}
}
$rs->close();
return $categories;
}
/**
* Returns an array of category ids of all the subcategories for a given
* category.
*
* @global object
* @param int $catid - The id of the category whose subcategories we want to find.
* @return array of category ids.
*/
function get_all_subcategories($catid) {
global $DB;
$subcats = array();
if ($categories = $DB->get_records('course_categories', array('parent'=>$catid))) {
foreach ($categories as $cat) {
array_push($subcats, $cat->id);
$subcats = array_merge($subcats, get_all_subcategories($cat->id));
}
}
return $subcats;
}
/**
* Return specified category, default if given does not exist
*
* @global object
* @uses MAX_COURSES_IN_CATEGORY
* @uses CONTEXT_COURSECAT
* @uses SYSCONTEXTID
* @param int $catid course category id
* @return object caregory
*/
function get_course_category($catid=0) {
global $DB;
$category = false;
if (!empty($catid)) {
$category = $DB->get_record('course_categories', array('id'=>$catid));
}
if (!$category) {
// the first category is considered default for now
if ($category = $DB->get_records('course_categories', null, 'sortorder', '*', 0, 1)) {
$category = reset($category);
} else {
$cat = new stdClass();
$cat->name = get_string('miscellaneous');
$cat->depth = 1;
$cat->sortorder = MAX_COURSES_IN_CATEGORY;
$cat->timemodified = time();
$catid = $DB->insert_record('course_categories', $cat);
// make sure category context exists
context_coursecat::instance($catid);
mark_context_dirty('/'.SYSCONTEXTID);
fix_course_sortorder(); // Required to build course_categories.depth and .path.
$category = $DB->get_record('course_categories', array('id'=>$catid));
}
}
return $category;
}
/**
* Fixes course category and course sortorder, also verifies category and course parents and paths.
* (circular references are not fixed)
@ -1085,9 +956,14 @@ function fix_course_sortorder() {
//WARNING: this is PHP5 only code!
// if there are any changes made to courses or categories we will trigger
// the cache events to purge all cached courses/categories data
$cacheevents = array();
if ($unsorted = $DB->get_records('course_categories', array('sortorder'=>0))) {
//move all categories that are not sorted yet to the end
$DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('sortorder'=>0));
$cacheevents['changesincoursecat'] = true;
}
$allcats = $DB->get_records('course_categories', null, 'sortorder, id', 'id, sortorder, parent, depth, path');
@ -1127,7 +1003,9 @@ function fix_course_sortorder() {
// now walk recursively the tree and fix any problems found
$sortorder = 0;
$fixcontexts = array();
_fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts);
if (_fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts)) {
$cacheevents['changesincoursecat'] = true;
}
// detect if there are "multiple" frontpage courses and fix them if needed
$frontcourses = $DB->get_records('course', array('category'=>0), 'id');
@ -1143,6 +1021,7 @@ function fix_course_sortorder() {
$DB->set_field('course', 'category', $defaultcat->id, array('id'=>$course->id));
$context = context_course::instance($course->id);
$fixcontexts[$context->id] = $context;
$cacheevents['changesincourse'] = true;
}
unset($frontcourses);
} else {
@ -1156,6 +1035,8 @@ function fix_course_sortorder() {
}
context_helper::build_all_paths(false);
unset($fixcontexts);
$cacheevents['changesincourse'] = true;
$cacheevents['changesincoursecat'] = true;
}
// release memory
@ -1166,6 +1047,7 @@ function fix_course_sortorder() {
// fix frontpage course sortorder
if ($frontcourse->sortorder != 1) {
$DB->set_field('course', 'sortorder', 1, array('id'=>$frontcourse->id));
$cacheevents['changesincourse'] = true;
}
// now fix the course counts in category records if needed
@ -1190,6 +1072,7 @@ function fix_course_sortorder() {
$str = implode(', ', $categories);
debugging("The number of courses (category id: $str) has reached MAX_COURSES_IN_CATEGORY (" . MAX_COURSES_IN_CATEGORY . "), it will cause a sorting performance issue, please increase the value of MAX_COURSES_IN_CATEGORY in lib/datalib.php file. See tracker issue: MDL-25669", DEBUG_DEVELOPER);
}
$cacheevents['changesincoursecat'] = true;
}
// now make sure that sortorders in course table are withing the category sortorder ranges
@ -1206,6 +1089,7 @@ function fix_course_sortorder() {
WHERE category = ?";
$DB->execute($sql, array($cat->sortorder, $cat->id));
}
$cacheevents['changesincoursecat'] = true;
}
unset($fixcategories);
@ -1239,6 +1123,7 @@ function fix_course_sortorder() {
// it needs full resorting
$fixcategories[$cat->id] = $cat;
}
$cacheevents['changesincourse'] = true;
}
unset($gapcategories);
@ -1250,10 +1135,16 @@ function fix_course_sortorder() {
if ($course->sortorder != $cat->sortorder + $i) {
$course->sortorder = $cat->sortorder + $i;
$DB->update_record_raw('course', $course, true);
$cacheevents['changesincourse'] = true;
}
$i++;
}
}
// advise all caches that need to be rebuilt
foreach (array_keys($cacheevents) as $event) {
cache_helper::purge_by_event($event);
}
}
/**
@ -1270,12 +1161,13 @@ function fix_course_sortorder() {
* @param int $depth
* @param string $path
* @param array $fixcontexts
* @return void
* @return bool if changes were made
*/
function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixcontexts) {
global $DB;
$depth++;
$changesmade = false;
foreach ($children as $cat) {
$sortorder = $sortorder + MAX_COURSES_IN_CATEGORY;
@ -1296,11 +1188,15 @@ function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixc
}
if ($update) {
$DB->update_record('course_categories', $cat, true);
$changesmade = true;
}
if (isset($cat->children)) {
_fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts);
if (_fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts)) {
$changesmade = true;
}
}
}
return $changesmade;
}
/**

View File

@ -198,4 +198,31 @@ $definitions = array(
'persistentmaxsize' => 1,
),
// Used to store the full tree of course categories
'coursecattree' => array(
'mode' => cache_store::MODE_APPLICATION,
'persistent' => true,
'invalidationevents' => array(
'changesincoursecat',
)
),
// Used to store data for course categories visible to current user. Helps to browse list of categories
'coursecat' => array(
'mode' => cache_store::MODE_SESSION,
'persistent' => true,
'invalidationevents' => array(
'changesincoursecat',
'changesincourse',
),
'ttl' => 600,
),
// Used to store data for course categories visible to current user. Helps to browse list of categories
'coursecatrecords' => array(
'mode' => cache_store::MODE_REQUEST,
'simplekeys' => true,
'persistent' => true,
'invalidationevents' => array(
'changesincoursecat',
),
),
);

View File

@ -3470,3 +3470,455 @@ function update_category_button($categoryid = 0) {
}
return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
}
/**
* This function recursively travels the categories, building up a nice list
* for display. It also makes an array that list all the parents for each
* category.
*
* For example, if you have a tree of categories like:
* Miscellaneous (id = 1)
* Subcategory (id = 2)
* Sub-subcategory (id = 4)
* Other category (id = 3)
* Then after calling this function you will have
* $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
* 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
* 3 => 'Other category');
* $parents = array(2 => array(1), 4 => array(1, 2));
*
* If you specify $requiredcapability, then only categories where the current
* user has that capability will be added to $list, although all categories
* will still be added to $parents, and if you only have $requiredcapability
* in a child category, not the parent, then the child catgegory will still be
* included.
*
* If you specify the option $excluded, then that category, and all its children,
* are omitted from the tree. This is useful when you are doing something like
* moving categories, where you do not want to allow people to move a category
* to be the child of itself.
*
* This function is deprecated! For list of categories use
* coursecat::make_all_categories($requiredcapability, $excludeid, $separator)
* For parents of one particular category use
* coursecat::get($id)->get_parents()
*
* @deprecated since 2.5
*
* @param array $list For output, accumulates an array categoryid => full category path name
* @param array $parents For output, accumulates an array categoryid => list of parent category ids.
* @param string/array $requiredcapability if given, only categories where the current
* user has this capability will be added to $list. Can also be an array of capabilities,
* in which case they are all required.
* @param integer $excludeid Omit this category and its children from the lists built.
* @param object $category Not used
* @param string $path Not used
*/
function make_categories_list(&$list, &$parents, $requiredcapability = '',
$excludeid = 0, $category = NULL, $path = "") {
global $CFG, $DB;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Global function make_categories_list() is deprecated. Please use '.
'coursecat::make_categories_list() and coursecat::get_parents()',
DEBUG_DEVELOPER);
// For categories list use just this one function:
if (empty($list)) {
$list = array();
}
$list += coursecat::make_categories_list($requiredcapability, $excludeid);
// Building the list of all parents of all categories in the system is highly undesirable and hardly ever needed.
// Usually user needs only parents for one particular category, in which case should be used:
// coursecat::get($categoryid)->get_parents()
if (empty($parents)) {
$parents = array();
}
$all = $DB->get_records_sql('SELECT id, parent FROM {course_categories} ORDER BY sortorder');
foreach ($all as $record) {
if ($record->parent) {
$parents[$record->id] = array_merge($parents[$record->parent], array($record->parent));
} else {
$parents[$record->id] = array();
}
}
}
/**
* Delete category, but move contents to another category.
*
* This function is deprecated. Please use
* coursecat::get($category->id)->delete_move($newparentid, $showfeedback);
*
* @see coursecat::delete_move()
* @deprecated since 2.5
*
* @param object $category
* @param int $newparentid category id
* @return bool status
*/
function category_delete_move($category, $newparentid, $showfeedback=true) {
global $CFG;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Function category_delete_move() is deprecated. Please use coursecat::delete_move() instead.');
return coursecat::get($category->id)->delete_move($newparentid, $showfeedback);
}
/**
* Recursively delete category including all subcategories and courses.
*
* This function is deprecated. Please use
* coursecat::get($category->id)->delete_full($showfeedback);
*
* @see coursecat::delete_full()
* @deprecated since 2.5
*
* @param stdClass $category
* @param boolean $showfeedback display some notices
* @return array return deleted courses
*/
function category_delete_full($category, $showfeedback=true) {
global $CFG, $DB;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Function category_delete_full() is deprecated. Please use coursecat::delete_full() instead.');
return coursecat::get($category->id)->delete_full($showfeedback);
}
/**
* Efficiently moves a category - NOTE that this can have
* a huge impact access-control-wise...
*
* This function is deprecated. Please use
* $coursecat = coursecat::get($category->id);
* if ($coursecat->can_change_parent($newparentcat->id)) {
* $coursecat->change_parent($newparentcat->id);
* }
*
* Alternatively you can use
* $coursecat->update(array('parent' => $newparentcat->id));
*
* Function update() also updates field course_categories.timemodified
*
* @see coursecat::change_parent()
* @see coursecat::update()
* @deprecated since 2.5
*
* @param stdClass|coursecat $category
* @param stdClass|coursecat $newparentcat
*/
function move_category($category, $newparentcat) {
global $CFG;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Function move_category() is deprecated. Please use coursecat::change_parent() instead.');
return coursecat::get($category->id)->change_parent($newparentcat->id);
}
/**
* Hide course category and child course and subcategories
*
* This function is deprecated. Please use
* coursecat::get($category->id)->hide();
*
* @see coursecat::hide()
* @deprecated since 2.5
*
* @param stdClass $category
* @return void
*/
function course_category_hide($category) {
global $CFG;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Function course_category_hide() is deprecated. Please use coursecat::hide() instead.');
coursecat::get($category->id)->hide();
}
/**
* Show course category and child course and subcategories
*
* This function is deprecated. Please use
* coursecat::get($category->id)->show();
*
* @see coursecat::show()
* @deprecated since 2.5
*
* @param stdClass $category
* @return void
*/
function course_category_show($category) {
global $CFG;
require_once($CFG->libdir.'/coursecatlib.php');
debugging('Function course_category_show() is deprecated. Please use coursecat::show() instead.');
coursecat::get($category->id)->show();
}
/**
* Return specified category, default if given does not exist
*
* This function is deprecated.
* To get the category with the specified it please use:
* coursecat::get($catid, IGNORE_MISSING);
* or
* coursecat::get($catid, MUST_EXIST);
*
* To get the first available category please use
* coursecat::get_default();
*
* class coursecat will also make sure that at least one category exists in DB
*
* @deprecated since 2.5
* @see coursecat::get()
* @see coursecat::get_default()
*
* @param int $catid course category id
* @return object caregory
*/
function get_course_category($catid=0) {
global $DB;
debugging('Function get_course_category() is deprecated. Please use coursecat::get(), see phpdocs for more details');
$category = false;
if (!empty($catid)) {
$category = $DB->get_record('course_categories', array('id'=>$catid));
}
if (!$category) {
// the first category is considered default for now
if ($category = $DB->get_records('course_categories', null, 'sortorder', '*', 0, 1)) {
$category = reset($category);
} else {
$cat = new stdClass();
$cat->name = get_string('miscellaneous');
$cat->depth = 1;
$cat->sortorder = MAX_COURSES_IN_CATEGORY;
$cat->timemodified = time();
$catid = $DB->insert_record('course_categories', $cat);
// make sure category context exists
context_coursecat::instance($catid);
mark_context_dirty('/'.SYSCONTEXTID);
fix_course_sortorder(); // Required to build course_categories.depth and .path.
$category = $DB->get_record('course_categories', array('id'=>$catid));
}
}
return $category;
}
/**
* Create a new course category and marks the context as dirty
*
* This function does not set the sortorder for the new category and
* {@link fix_course_sortorder()} should be called after creating a new course
* category
*
* Please note that this function does not verify access control.
*
* This function is deprecated. It is replaced with the method create() in class coursecat.
* {@link coursecat::create()} also verifies the data, fixes sortorder and logs the action
*
* @deprecated since 2.5
*
* @param object $category All of the data required for an entry in the course_categories table
* @return object new course category
*/
function create_course_category($category) {
global $DB;
debugging('Function create_course_category() is deprecated. Please use coursecat::create(), see phpdocs for more details', DEBUG_DEVELOPER);
$category->timemodified = time();
$category->id = $DB->insert_record('course_categories', $category);
$category = $DB->get_record('course_categories', array('id' => $category->id));
// We should mark the context as dirty
$category->context = context_coursecat::instance($category->id);
$category->context->mark_dirty();
return $category;
}
/**
* Returns an array of category ids of all the subcategories for a given
* category.
*
* This function is deprecated.
*
* To get visible children categories of the given category use:
* coursecat::get($categoryid)->get_children();
* This function will return the array or coursecat objects, on each of them
* you can call get_children() again
*
* @see coursecat::get()
* @see coursecat::get_children()
*
* @deprecated since 2.5
*
* @global object
* @param int $catid - The id of the category whose subcategories we want to find.
* @return array of category ids.
*/
function get_all_subcategories($catid) {
global $DB;
debugging('Function get_all_subcategories() is deprecated. Please use appropriate methods() of coursecat class. See phpdocs for more details',
DEBUG_DEVELOPER);
$subcats = array();
if ($categories = $DB->get_records('course_categories', array('parent' => $catid))) {
foreach ($categories as $cat) {
array_push($subcats, $cat->id);
$subcats = array_merge($subcats, get_all_subcategories($cat->id));
}
}
return $subcats;
}
/**
* Gets the child categories of a given courses category
*
* This function is deprecated. Please use functions in class coursecat:
* - coursecat::get($parentid)->has_children()
* tells if the category has children (visible or not to the current user)
*
* - coursecat::get($parentid)->get_children()
* returns an array of coursecat objects, each of them represents a children category visible
* to the current user (i.e. visible=1 or user has capability to view hidden categories)
*
* - coursecat::get($parentid)->get_children_count()
* returns number of children categories visible to the current user
*
* - coursecat::count_all()
* returns total count of all categories in the system (both visible and not)
*
* - coursecat::get_default()
* returns the first category (usually to be used if count_all() == 1)
*
* @deprecated since 2.5
*
* @param int $parentid the id of a course category.
* @return array all the child course categories.
*/
function get_child_categories($parentid) {
global $DB;
debugging('Function get_child_categories() is deprecated. Use coursecat::get_children() or see phpdocs for more details.',
DEBUG_DEVELOPER);
$rv = array();
$sql = context_helper::get_preload_record_columns_sql('ctx');
$records = $DB->get_records_sql("SELECT c.*, $sql FROM {course_categories} c ".
"JOIN {context} ctx on ctx.instanceid = c.id AND ctx.contextlevel = ? WHERE c.parent = ? ORDER BY c.sortorder",
array(CONTEXT_COURSECAT, $parentid));
foreach ($records as $category) {
context_helper::preload_from_record($category);
if (!$category->visible && !has_capability('moodle/category:viewhiddencategories', context_coursecat::instance($category->id))) {
continue;
}
$rv[] = $category;
}
return $rv;
}
/**
* Returns a sorted list of categories.
*
* When asking for $parent='none' it will return all the categories, regardless
* of depth. Wheen asking for a specific parent, the default is to return
* a "shallow" resultset. Pass false to $shallow and it will return all
* the child categories as well.
*
* @deprecated since 2.5
*
* This function is deprecated. Use appropriate functions from class coursecat.
* Examples:
*
* coursecat::get($categoryid)->get_children()
* - returns all children of the specified category as instances of class
* coursecat, which means on each of them method get_children() can be called again
*
* coursecat::get($categoryid)->get_children(array('recursive' => true))
* - returns all children of the specified category and all subcategories
*
* coursecat::get(0)->get_children(array('recursive' => true))
* - returns all categories defined in the system
*
* Sort fields can be specified, see phpdocs to {@link coursecat::get_children()}
*
* Also see functions {@link coursecat::get_children_count()}, {@link coursecat::count_all()},
* {@link coursecat::get_default()}
*
* The code of this deprecated function is left as it is because coursecat::get_children()
* returns categories as instances of coursecat and not stdClass
*
* @param string $parent The parent category if any
* @param string $sort the sortorder
* @param bool $shallow - set to false to get the children too
* @return array of categories
*/
function get_categories($parent='none', $sort=NULL, $shallow=true) {
global $DB;
debugging('Function get_categories() is deprecated. Please use coursecat::get_children(). See phpdocs for more details',
DEBUG_DEVELOPER);
if ($sort === NULL) {
$sort = 'ORDER BY cc.sortorder ASC';
} elseif ($sort ==='') {
// leave it as empty
} else {
$sort = "ORDER BY $sort";
}
list($ccselect, $ccjoin) = context_instance_preload_sql('cc.id', CONTEXT_COURSECAT, 'ctx');
if ($parent === 'none') {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
$sort";
$params = array();
} elseif ($shallow) {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
WHERE cc.parent=?
$sort";
$params = array($parent);
} else {
$sql = "SELECT cc.* $ccselect
FROM {course_categories} cc
$ccjoin
JOIN {course_categories} ccp
ON ((cc.parent = ccp.id) OR (cc.path LIKE ".$DB->sql_concat('ccp.path',"'/%'")."))
WHERE ccp.id=?
$sort";
$params = array($parent);
}
$categories = array();
$rs = $DB->get_recordset_sql($sql, $params);
foreach($rs as $cat) {
context_instance_preload($cat);
$catcontext = context_coursecat::instance($cat->id);
if ($cat->visible || has_capability('moodle/category:viewhiddencategories', $catcontext)) {
$categories[$cat->id] = $cat;
}
}
$rs->close();
return $categories;
}

View File

@ -404,8 +404,8 @@ function question_delete_course($course, $feedback=true) {
* 1/ All question categories and their questions are deleted for this course category.
* 2/ All questions are moved to new category
*
* @param object $category course category object
* @param object $newcategory empty means everything deleted, otherwise id of
* @param object|coursecat $category course category object
* @param object|coursecat $newcategory empty means everything deleted, otherwise id of
* category where content moved
* @param boolean $feedback to specify if the process must output a summary of its work
* @return boolean

View File

@ -220,11 +220,11 @@ EOD;
* Create a test course category
* @param array|stdClass $record
* @param array $options
* @return stdClass course category record
* @return coursecat course category record
*/
public function create_category($record=null, array $options=null) {
global $DB, $CFG;
require_once("$CFG->dirroot/course/lib.php");
require_once("$CFG->libdir/coursecatlib.php");
$this->categorycount++;
$i = $this->categorycount;
@ -235,43 +235,15 @@ EOD;
$record['name'] = 'Course category '.$i;
}
if (!isset($record['idnumber'])) {
$record['idnumber'] = '';
}
if (!isset($record['description'])) {
$record['description'] = "Test course category $i\n$this->loremipsum";
}
if (!isset($record['descriptionformat'])) {
$record['descriptionformat'] = FORMAT_MOODLE;
if (!isset($record['idnumber'])) {
$record['idnumber'] = '';
}
if (!isset($record['parent'])) {
$record['parent'] = 0;
}
if (empty($record['parent'])) {
$parent = new stdClass();
$parent->path = '';
$parent->depth = 0;
} else {
$parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
}
$record['depth'] = $parent->depth+1;
$record['sortorder'] = 0;
$record['timemodified'] = time();
$record['timecreated'] = $record['timemodified'];
$catid = $DB->insert_record('course_categories', $record);
$path = $parent->path . '/' . $catid;
$DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
context_coursecat::instance($catid);
fix_course_sortorder();
return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
return coursecat::create($record);
}
/**

View File

@ -253,20 +253,12 @@ class moodle_block_manager_test_saving_loading_testcase extends advanced_testcas
}
public function test_block_not_included_in_different_context() {
global $DB;
$this->purge_blocks();
// Set up fixture.
$syscontext = context_system::instance();
$cat = new stdClass();
$cat->name = 'testcategory';
$cat->parent = 0;
$cat->depth = 1;
$cat->sortorder = 100;
$cat->timemodified = time();
$catid = $DB->insert_record('course_categories', $cat);
$DB->set_field('course_categories', 'path', '/' . $catid, array('id' => $catid));
$fakecontext = context_coursecat::instance($catid);
$cat = $this->getDataGenerator()->create_category(array('name' => 'testcategory'));
$fakecontext = context_coursecat::instance($cat->id);
$regionname = 'a-region';
$blockname = $this->get_a_known_block_type();

View File

@ -0,0 +1,421 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tests for class coursecat from lib/coursecatlib.php
*
* @package core
* @category phpunit
* @copyright 2013 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/coursecatlib.php');
/**
* Functional test for accesslib.php
*
* Note: execution may take many minutes especially on slower servers.
*/
class coursecatlib_testcase extends advanced_testcase {
var $roles;
public function setUp() {
parent::setUp();
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
}
protected function get_roleid($context = null) {
global $USER;
if ($context === null) {
$context = context_system::instance();
}
if (is_object($context)) {
$context = $context->id;
}
if (empty($this->roles)) {
$this->roles = array();
}
if (empty($this->roles[$USER->id])) {
$this->roles[$USER->id] = array();
}
if (empty($this->roles[$USER->id][$context])) {
$this->roles[$USER->id][$context] = create_role('Role for '.$USER->id.' in '.$context, 'role'.$USER->id.'-'.$context, '-');
role_assign($this->roles[$USER->id][$context], $USER->id, $context);
}
return $this->roles[$USER->id][$context];
}
protected function assign_capability($capability, $permission = CAP_ALLOW, $contextid = null) {
if ($contextid === null) {
$contextid = context_system::instance();
}
if (is_object($contextid)) {
$contextid = $contextid->id;
}
assign_capability($capability, $permission, $this->get_roleid($contextid), $contextid, true);
accesslib_clear_all_caches_for_unit_testing();
}
public function test_create_coursecat() {
// Create the category
$data = new stdClass();
$data->name = 'aaa';
$data->description = 'aaa';
$data->idnumber = '';
$category1 = coursecat::create($data);
// Initially confirm that base data was inserted correctly
$this->assertEquals($data->name, $category1->name);
$this->assertEquals($data->description, $category1->description);
$this->assertEquals($data->idnumber, $category1->idnumber);
$this->assertGreaterThanOrEqual(1, $category1->sortorder);
// Create two more categories and test the sortorder worked correctly
$data->name = 'ccc';
$category2 = coursecat::create($data);
$data->name = 'bbb';
$category3 = coursecat::create($data);
$this->assertGreaterThan($category1->sortorder, $category2->sortorder);
$this->assertGreaterThan($category2->sortorder, $category3->sortorder);
}
public function test_name_idnumber_exceptions() {
try {
coursecat::create(array('name' => ''));
$this->fail('Missing category name exception expected in coursecat::create');
} catch (moodle_exception $e) {
}
$cat1 = coursecat::create(array('name' => 'Cat1', 'idnumber' => '1'));
try {
$cat1->update(array('name' => ''));
$this->fail('Missing category name exception expected in coursecat::update');
} catch (moodle_exception $e) {
}
try {
coursecat::create(array('name' => 'Cat2', 'idnumber' => '1'));
$this->fail('Duplicate idnumber exception expected in coursecat::create');
} catch (moodle_exception $e) {
}
$cat2 = coursecat::create(array('name' => 'Cat2', 'idnumber' => '2'));
try {
$cat2->update(array('idnumber' => '1'));
$this->fail('Duplicate idnumber exception expected in coursecat::update');
} catch (moodle_exception $e) {
}
}
public function test_visibility() {
$this->assign_capability('moodle/category:viewhiddencategories');
$this->assign_capability('moodle/category:manage');
// create category 1 initially hidden
$category1 = coursecat::create(array('name' => 'Cat1', 'visible' => 0));
$this->assertEquals(0, $category1->visible);
$this->assertEquals(0, $category1->visibleold);
// create category 2 initially hidden as a child of hidden category 1
$category2 = coursecat::create(array('name' => 'Cat2', 'visible' => 0, 'parent' => $category1->id));
$this->assertEquals(0, $category2->visible);
$this->assertEquals(0, $category2->visibleold);
// create category 3 initially visible as a child of hidden category 1
$category3 = coursecat::create(array('name' => 'Cat3', 'visible' => 1, 'parent' => $category1->id));
$this->assertEquals(0, $category3->visible);
$this->assertEquals(1, $category3->visibleold);
// show category 1 and make sure that category 2 is hidden and category 3 is visible
$category1->show();
$this->assertEquals(1, coursecat::get($category1->id)->visible);
$this->assertEquals(0, coursecat::get($category2->id)->visible);
$this->assertEquals(1, coursecat::get($category3->id)->visible);
// create visible category 4
$category4 = coursecat::create(array('name' => 'Cat4'));
$this->assertEquals(1, $category4->visible);
$this->assertEquals(1, $category4->visibleold);
// create visible category 5 as a child of visible category 4
$category5 = coursecat::create(array('name' => 'Cat5', 'parent' => $category4->id));
$this->assertEquals(1, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
// hide category 4 and make sure category 5 is hidden too
$category4->hide();
$this->assertEquals(0, $category4->visible);
$this->assertEquals(0, $category4->visibleold);
$category5 = coursecat::get($category5->id); // we have to re-read from DB
$this->assertEquals(0, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
// show category 4 and make sure category 5 is visible too
$category4->show();
$this->assertEquals(1, $category4->visible);
$this->assertEquals(1, $category4->visibleold);
$category5 = coursecat::get($category5->id); // we have to re-read from DB
$this->assertEquals(1, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
// move category 5 under hidden category 2 and make sure it became hidden
$category5->change_parent($category2->id);
$this->assertEquals(0, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
// re-read object for category 5 from DB and check again
$category5 = coursecat::get($category5->id);
$this->assertEquals(0, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
// tricky one! Move hidden category 5 under visible category ("Top") and make sure it is still hidden
// WHY? Well, different people may expect different behaviour here. So better keep it hidden
$category5->change_parent(0);
$this->assertEquals(0, $category5->visible);
$this->assertEquals(1, $category5->visibleold);
}
public function test_hierarchy() {
$this->assign_capability('moodle/category:viewhiddencategories');
$this->assign_capability('moodle/category:manage');
$category1 = coursecat::create(array('name' => 'Cat1'));
$category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
$category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id));
$category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
// check function get_children()
$this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
// check function get_parents()
$this->assertEquals(array($category1->id, $category2->id), $category4->get_parents());
// can not move category to itself or to it's children
$this->assertFalse($category1->can_change_parent($category2->id));
$this->assertFalse($category2->can_change_parent($category2->id));
// can move category to grandparent
$this->assertTrue($category4->can_change_parent($category1->id));
try {
$category2->change_parent($category4->id);
$this->fail('Exception expected - can not move category');
} catch (moodle_exception $e) {
}
$category4->change_parent(0);
$this->assertEquals(array(), $category4->get_parents());
$this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
$this->assertEquals(array(), array_keys($category2->get_children()));
}
public function test_update() {
$category1 = coursecat::create(array('name' => 'Cat1'));
$timecreated = $category1->timemodified;
$this->assertEquals('Cat1', $category1->name);
$this->assertTrue(empty($category1->description));
sleep(2);
$testdescription = 'This is cat 1 а также русский текст';
$category1->update(array('description' => $testdescription));
$this->assertEquals($testdescription, $category1->description);
$category1 = coursecat::get($category1->id);
$this->assertEquals($testdescription, $category1->description);
cache_helper::purge_by_event('changesincoursecat');
$category1 = coursecat::get($category1->id);
$this->assertEquals($testdescription, $category1->description);
$this->assertGreaterThan($timecreated, $category1->timemodified);
}
public function test_delete() {
global $DB;
$this->assign_capability('moodle/category:manage');
$this->assign_capability('moodle/course:create');
$initialcatid = $DB->get_field_sql('SELECT max(id) from {course_categories}');
$category1 = coursecat::create(array('name' => 'Cat1'));
$category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
$category3 = coursecat::create(array('name' => 'Cat3'));
$category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
$course1 = $this->getDataGenerator()->create_course(array('category' => $category2->id));
$course2 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
$course3 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
$course4 = $this->getDataGenerator()->create_course(array('category' => $category1->id));
// Now we have
// $category1
// $category2
// $category4
// $course2
// $course3
// $course1
// $course4
// $category3
// Login as another user to test course:delete capability (user who created course can delete it within 24h even without cap)
$this->setUser($this->getDataGenerator()->create_user());
// Delete category 2 and move content to category 3
$this->assertFalse($category2->can_move_content_to($category3->id)); // no luck!
// add necessary capabilities
$this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($category3->id));
$this->assign_capability('moodle/category:manage');
$this->assertTrue($category2->can_move_content_to($category3->id)); // hurray!
$category2->delete_move($category3->id);
// Make sure we have:
// $category1
// $course4
// $category3
// $category4
// $course2
// $course3
// $course1
$this->assertNull(coursecat::get($category2->id, IGNORE_MISSING, true));
$this->assertEquals(array(), $category1->get_children());
$this->assertEquals(array($category4->id), array_keys($category3->get_children()));
$this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course2->id)));
$this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course3->id)));
$this->assertEquals($category3->id, $DB->get_field('course', 'category', array('id' => $course1->id)));
// Delete category 3 completely
$this->assertFalse($category3->can_delete_full()); // no luck!
// add necessary capabilities
$this->assign_capability('moodle/course:delete', CAP_ALLOW, context_coursecat::instance($category3->id));
$this->assertTrue($category3->can_delete_full()); // hurray!
$category3->delete_full();
// Make sure we have:
// $category1
// $course4
// Note that we also have default 'Miscellaneous' category and default 'site' course
$this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course_categories} WHERE id > ?', array($initialcatid)));
$this->assertEquals($category1->id, $DB->get_field_sql('SELECT max(id) FROM {course_categories}'));
$this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course} WHERE id <> ?', array(SITEID)));
$this->assertEquals(array('id' => $course4->id, 'category' => $category1->id),
(array)$DB->get_record_sql('SELECT id, category from {course} where id <> ?', array(SITEID)));
}
public function test_get_children() {
$category1 = coursecat::create(array('name' => 'Cat1'));
$category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
$category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id, 'visible' => 0));
$category4 = coursecat::create(array('name' => 'Cat4', 'idnumber' => '12', 'parent' => $category1->id));
$category5 = coursecat::create(array('name' => 'Cat5', 'idnumber' => '11', 'parent' => $category1->id, 'visible' => 0));
$category6 = coursecat::create(array('name' => 'Cat6', 'idnumber' => '10', 'parent' => $category1->id));
$category7 = coursecat::create(array('name' => 'Cat0', 'parent' => $category1->id));
$children = $category1->get_children();
// user does not have the capability to view hidden categories, so the list should be
// 2,4,6,7
$this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
$this->assertEquals(4, $category1->get_children_count());
$children = $category1->get_children(array('offset' => 2));
$this->assertEquals(array($category6->id, $category7->id), array_keys($children));
$this->assertEquals(4, $category1->get_children_count());
$children = $category1->get_children(array('limit' => 2));
$this->assertEquals(array($category2->id, $category4->id), array_keys($children));
$children = $category1->get_children(array('offset' => 1, 'limit' => 2));
$this->assertEquals(array($category4->id, $category6->id), array_keys($children));
$children = $category1->get_children(array('sort' => array('name' => 1)));
// must be 7,2,4,6
$this->assertEquals(array($category7->id, $category2->id, $category4->id, $category6->id), array_keys($children));
$children = $category1->get_children(array('sort' => array('idnumber' => 1, 'name' => -1)));
// must be 2,7,6,4
$this->assertEquals(array($category2->id, $category7->id, $category6->id, $category4->id), array_keys($children));
// check that everything is all right after purging the caches
cache_helper::purge_by_event('changesincoursecat');
$children = $category1->get_children();
$this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
$this->assertEquals(4, $category1->get_children_count());
}
public function test_get_search_courses() {
$cat1 = coursecat::create(array('name' => 'Cat1'));
$cat2 = coursecat::create(array('name' => 'Cat2', 'parent' => $cat1->id));
$c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => ' ', 'idnumber' => 'ID3'));
$c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => ' ', 'visible' => 0));
$c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test '));
$c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => ' ', 'idnumber' => 'ID4'));
$c5 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => ' '));
$c6 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => ' '));
$c7 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => ' ', 'visible' => 0));
$c8 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => ' '));
// get courses in category 1 (returned visible only because user is not enrolled) global $DB;
$res = $cat1->get_courses(array('sortorder' => 1));
$this->assertEquals(array($c4->id, $c3->id, $c1->id), array_keys($res)); // courses are added in reverse order
$this->assertEquals(3, $cat1->get_courses_count());
// get courses in category 1 recursively (returned visible only because user is not enrolled)
$res = $cat1->get_courses(array('recursive' => 1));
$this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c6->id, $c5->id), array_keys($res));
$this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1)));
// get courses sorted by fullname
$res = $cat1->get_courses(array('sort' => array('fullname' => 1)));
$this->assertEquals(array($c1->id, $c4->id, $c3->id), array_keys($res));
$this->assertEquals(3, $cat1->get_courses_count(array('sort' => array('fullname' => 1))));
// get courses sorted by fullname recursively
$res = $cat1->get_courses(array('recursive' => 1, 'sort' => array('fullname' => 1)));
$this->assertEquals(array($c1->id, $c4->id, $c5->id, $c8->id, $c6->id, $c3->id), array_keys($res));
$this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'sort' => array('fullname' => 1))));
// get courses sorted by fullname recursively, use offset and limit
$res = $cat1->get_courses(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => -1)));
$this->assertEquals(array($c6->id, $c8->id), array_keys($res));
// offset and limit do not affect get_courses_count()
$this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => 1))));
// calling get_courses_count without prior call to get_courses()
$this->assertEquals(3, $cat2->get_courses_count(array('recursive' => 1, 'sort' => array('idnumber' => 1))));
// search courses
// search by text
$res = coursecat::search_courses(array('search' => 'test'));
$this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
$this->assertEquals(5, coursecat::search_courses_count(array('search' => 'test')));
$res = coursecat::search_courses(array('search' => 'Математика'));
$this->assertEquals(array($c3->id, $c6->id), array_keys($res));
$this->assertEquals(2, coursecat::search_courses_count(array('search' => 'Математика'), array()));
$options = array('sort' => array('fullname' => 1), 'offset' => 1, 'limit' => 2);
$res = coursecat::search_courses(array('search' => 'test'), $options);
$this->assertEquals(array($c4->id, $c5->id), array_keys($res));
$this->assertEquals(5, coursecat::search_courses_count(array('search' => 'test'), $options));
}
}

View File

@ -380,26 +380,31 @@ class moodle_page_categories_test extends advanced_testcase {
}
public function test_set_category_top_level() {
global $DB;
// Setup fixture
$cat = $this->getDataGenerator()->create_category();
$catdbrecord = $DB->get_record('course_categories', array('id' => $cat->id));
// Exercise SUT
$this->testpage->set_category_by_id($cat->id);
// Validate
$this->assertEquals($cat, $this->testpage->category);
$this->assertEquals($catdbrecord, $this->testpage->category);
$this->assertSame(context_coursecat::instance($cat->id), $this->testpage->context);
}
public function test_set_nested_categories() {
global $DB;
// Setup fixture
$topcat = $this->getDataGenerator()->create_category();
$topcatdbrecord = $DB->get_record('course_categories', array('id' => $topcat->id));
$subcat = $this->getDataGenerator()->create_category(array('parent'=>$topcat->id));
$subcatdbrecord = $DB->get_record('course_categories', array('id' => $subcat->id));
// Exercise SUT
$this->testpage->set_category_by_id($subcat->id);
// Validate
$categories = $this->testpage->categories;
$this->assertEquals(2, count($categories));
$this->assertEquals($topcat, array_pop($categories));
$this->assertEquals($subcat, array_pop($categories));
$this->assertEquals($topcatdbrecord, array_pop($categories));
$this->assertEquals($subcatdbrecord, array_pop($categories));
}
}

View File

@ -37,6 +37,11 @@ information provided here is intended especially for developers.
* remove all references to $CFG->gdversion, GD PHP extension is now required
* Formslib will now throw a developer warning if a PARAM_ type hasn't been set for elements which
need it. Please set PARAM_RAW explicitly if you do not want any cleaning.
* Functions responsible for managing and accessing course categories are moved to class coursecat
in lib/coursecatlib.php. The following global functions are deprecated: make_categories_list(),
category_delete_move(), category_delete_full(), move_category(), course_category_hide(),
course_category_show(), get_course_category(), create_course_category(), get_all_subcategories(),
get_child_categories(), get_categories()
YUI changes:
* M.util.help_icon has been deprecated. Code should be updated to use moodle-core-popuphelp

View File

@ -32,11 +32,8 @@ class user_filter_courserole extends user_filter_type {
*/
function get_course_categories() {
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
$displaylist = array();
$parentlist = array();
make_categories_list($displaylist, $parentlist);
return array(0=> get_string('anycategory', 'filters')) + $displaylist;
require_once($CFG->libdir.'/coursecatlib.php');
return array(0=> get_string('anycategory', 'filters')) + coursecat::make_categories_list();
}
/**