Skip to content

Commit

Permalink
MDL-67548 core_course: Add more category deletion hooks.
Browse files Browse the repository at this point in the history
Introduce new hooks for plugin developers:

  - <component>_can_course_category_delete($category)
  - <component>_can_course_category_delete_move($category, $newcategory)
These hooks allow plugin developers greater control over category deletion. Plugin can return false in those
functions if category deletion or deletion with content move to the new parent category is not permitted.

  - <component>_pre_course_category_delete_move($category, $newcategory)
This hook is expanding functionality of existing <component>_pre_course_category_delete hook and allow plugin developers
to execute code prior to category deletion when its content is moved to another category.

 - <component>_get_course_category_contents($category)
This hook allow plugin developers to add information that is displayed on category deletion form. Function should
return string, which will be added to the list of category contents shown on the form.
  • Loading branch information
kabalin committed May 12, 2020
1 parent 27b29b2 commit 72316a9
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 20 deletions.
72 changes: 55 additions & 17 deletions course/classes/category.php
Expand Up @@ -171,6 +171,24 @@ public function __unset($name) {
debugging('Can not unset core_course_category instance properties!', DEBUG_DEVELOPER);
}

/**
* Get list of plugin callback functions.
*
* @param string $name Callback function name.
* @return [callable] $pluginfunctions
*/
public function get_plugins_callback_function(string $name) : array {
$pluginfunctions = [];
if ($pluginsfunction = get_plugins_with_function($name)) {
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
$pluginfunctions[] = $pluginfunction;
}
}
}
return $pluginfunctions;
}

/**
* Create an iterator because magic vars can't be seen by 'foreach'.
*
Expand Down Expand Up @@ -1900,13 +1918,12 @@ public function can_delete_full() {
return false;
}

$context = $this->get_context();
if (!$this->is_uservisible() ||
!has_capability('moodle/category:manage', $context)) {
if (!$this->has_manage_capability()) {
return false;
}

// Check all child categories (not only direct children).
$context = $this->get_context();
$sql = context_helper::get_preload_record_columns_sql('ctx');
$childcategories = $DB->get_records_sql('SELECT c.id, c.visible, '. $sql.
' FROM {context} ctx '.
Expand Down Expand Up @@ -1936,6 +1953,15 @@ public function can_delete_full() {
}
}

// Check if plugins permit deletion of category content.
$pluginfunctions = $this->get_plugins_callback_function('can_course_category_delete');
foreach ($pluginfunctions as $pluginfunction) {
// If at least one plugin does not permit deletion, stop and return false.
if (!$pluginfunction($this)) {
return false;
}
}

return true;
}

Expand All @@ -1961,13 +1987,9 @@ public function delete_full($showfeedback = true) {
$settimeout = core_php_time_limit::raise();

// Allow plugins to use this category before we completely delete it.
if ($pluginsfunction = get_plugins_with_function('pre_course_category_delete')) {
$category = $this->get_db_record();
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
$pluginfunction($category);
}
}
$pluginfunctions = $this->get_plugins_callback_function('pre_course_category_delete');
foreach ($pluginfunctions as $pluginfunction) {
$pluginfunction($this->get_db_record());
}

$deletedcourses = array();
Expand Down Expand Up @@ -2072,25 +2094,35 @@ public function move_content_targets_list() {
public function can_move_content_to($newcatid) {
global $CFG;
require_once($CFG->libdir . '/questionlib.php');
$context = $this->get_context();
if (!$this->is_uservisible() ||
!has_capability('moodle/category:manage', $context)) {

if (!$this->has_manage_capability()) {
return false;
}

$testcaps = array();
// If this category has courses in it, user must have 'course:create' capability in target category.
if ($this->has_courses()) {
$testcaps[] = 'moodle/course:create';
}
// If this category has subcategories or questions, user must have 'category:manage' capability in target category.
if ($this->has_children() || question_context_has_any_questions($context)) {
if ($this->has_children() || question_context_has_any_questions($this->get_context())) {
$testcaps[] = 'moodle/category:manage';
}
if (!empty($testcaps)) {
return has_all_capabilities($testcaps, context_coursecat::instance($newcatid));
if (!empty($testcaps) && !has_all_capabilities($testcaps, context_coursecat::instance($newcatid))) {
// No sufficient capabilities to perform this task.
return false;
}

// Check if plugins permit moving category content.
$pluginfunctions = $this->get_plugins_callback_function('can_course_category_delete_move');
$newparentcat = self::get($newcatid, MUST_EXIST, true);
foreach ($pluginfunctions as $pluginfunction) {
// If at least one plugin does not permit move on deletion, stop and return false.
if (!$pluginfunction($this, $newparentcat)) {
return false;
}
}

// There is no content but still return true.
return true;
}

Expand Down Expand Up @@ -2120,6 +2152,12 @@ public function delete_move($newparentid, $showfeedback = false) {
$coursesids = $DB->get_fieldset_select('course', 'id', 'category = :category ORDER BY sortorder ASC', $params);
$context = $this->get_context();

// Allow plugins to make necessary changes before we move the category content.
$pluginfunctions = $this->get_plugins_callback_function('pre_course_category_delete_move');
foreach ($pluginfunctions as $pluginfunction) {
$pluginfunction($this, $newparentcat);
}

if ($children) {
foreach ($children as $childcat) {
$childcat->change_parent_raw($newparentcat);
Expand Down
22 changes: 19 additions & 3 deletions course/classes/deletecategory_form.php
Expand Up @@ -76,14 +76,23 @@ public function definition() {
// Describe the contents of this category.
$contents = '';
if ($this->coursecat->has_children()) {
$contents .= '<li>' . get_string('subcategories') . '</li>';
$contents .= html_writer::tag('li', get_string('subcategories'));
}
if ($this->coursecat->has_courses()) {
$contents .= '<li>' . get_string('courses') . '</li>';
$contents .= html_writer::tag('li', get_string('courses'));
}
if (question_context_has_any_questions($categorycontext)) {
$contents .= '<li>' . get_string('questionsinthequestionbank') . '</li>';
$contents .= html_writer::tag('li', get_string('questionsinthequestionbank'));
}

// Check if plugins can provide more info.
$pluginfunctions = $this->coursecat->get_plugins_callback_function('get_course_category_contents');
foreach ($pluginfunctions as $pluginfunction) {
if ($plugincontents = $pluginfunction($this->coursecat)) {
$contents .= html_writer::tag('li', $plugincontents);
}
}

if (!empty($contents)) {
$mform->addElement('static', 'emptymessage', get_string('thiscategorycontains'), html_writer::tag('ul', $contents));
} else {
Expand All @@ -92,7 +101,9 @@ public function definition() {

// Give the options for what to do.
$mform->addElement('select', 'fulldelete', get_string('whattodo'), $options);

if (count($options) == 1) {
// Freeze selector if only one option available.
$optionkeys = array_keys($options);
$option = reset($optionkeys);
$mform->hardFreeze('fulldelete');
Expand Down Expand Up @@ -127,6 +138,11 @@ public function validation($data, $files) {
if (empty($data['fulldelete']) && empty($data['newparent'])) {
// When they have chosen the move option, they must specify a destination.
$errors['newparent'] = get_string('required');
return $errors;
}

if (!empty($data['newparent']) && !$this->coursecat->can_move_content_to($data['newparent'])) {
$errors['newparent'] = get_string('movecatcontentstoselected', 'error');
}

return $errors;
Expand Down
1 change: 1 addition & 0 deletions lang/en/error.php
Expand Up @@ -405,6 +405,7 @@
$string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
$string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
$string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existing category!';
$string['movecatcontentstoselected'] = 'Some of the category content can not be moved into selected category.';
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
$string['movecategoryownparent'] = 'You cannot make category \'{$a}\' a parent of itself.';
$string['movecategoryparentconflict'] = 'You cannot make category \'{$a}\' a subcategory of one of its own subcategories.';
Expand Down
14 changes: 14 additions & 0 deletions lib/upgrade.txt
Expand Up @@ -53,6 +53,20 @@ information provided here is intended especially for developers.
The confirmation dialogue no longer has a configurable "No" button as per similar changes in MDL-59759.
This set of confirmation modals was unintentionally missed from that deprecation process.
* The download_as_dataformat() method has been deprecated. Please use \core\dataformat::download_data() instead
* Introduce new hooks for plugin developers:
- <component>_can_course_category_delete($category)
- <component>_can_course_category_delete_move($category, $newcategory)
These hooks allow plugin developers greater control over category deletion. Plugin can return false in those
functions if category deletion or deletion with content move to the new parent category is not permitted.
Both $category and $newcategory params are instances of core_course_category class.
- <component>_pre_course_category_delete_move($category, $newcategory)
This hook is expanding functionality of existing <component>_pre_course_category_delete hook and allow plugin developers
to execute code prior to category deletion when its content is moved to another category.
Both $category and $newcategory params are instances of core_course_category class.
- <component>_get_course_category_contents($category)
This hook allow plugin developers to add information that is displayed on category deletion form. Function should
return string, which will be added to the list of category contents shown on the form. $category param is an instance
of core_course_category class.

=== 3.8 ===
* Add CLI option to notify all cron tasks to stop: admin/cli/cron.php --stop
Expand Down

0 comments on commit 72316a9

Please sign in to comment.