MDL-73549 Course: My course page menu improvement

- Introduce core_course_category::get_nearest_editable_subcategory()
 - This function will return the first creatable/manageable category
for current user
 - With this new function, we can fix the issue that the users with
course management or creation permision at category level cannot see
the manage menu on My courses page
This commit is contained in:
Huong Nguyen 2022-03-02 13:16:18 +07:00
parent 1d99ba19a2
commit 481cfdc3f0
6 changed files with 251 additions and 9 deletions

View file

@ -3100,6 +3100,36 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
return course_request::can_request($this->get_context());
}
/**
* Returns true if the user has all the given permissions.
*
* @param array $permissionstocheck The value can be create, manage or any specific capability.
* @return bool
*/
private function has_capabilities(array $permissionstocheck): bool {
if (empty($permissionstocheck)) {
throw new coding_exception('Invalid permissionstocheck parameter');
}
foreach ($permissionstocheck as $permission) {
if ($permission == 'create') {
if (!$this->can_create_course()) {
return false;
}
} else if ($permission == 'manage') {
if (!$this->has_manage_capability()) {
return false;
}
} else {
// Specific capability.
if (!$this->is_uservisible() || !has_capability($permission, $this->get_context())) {
return false;
}
}
}
return true;
}
/**
* Returns true if the user can approve course requests.
* @return bool
@ -3146,4 +3176,32 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
}
}
}
/**
* Returns the core_course_category object for the first category that the current user have the permission for the course.
*
* Only returns if it exists and is creatable/manageable to the current user
*
* @param core_course_category $parentcat Parent category to check.
* @param array $permissionstocheck The value can be create, manage or any specific capability.
* @return core_course_category|null
*/
public static function get_nearest_editable_subcategory(core_course_category $parentcat,
array $permissionstocheck): ?core_course_category {
// First, check the parent category.
if ($parentcat->has_capabilities($permissionstocheck)) {
return $parentcat;
}
// Check the child categories.
$subcategoryids = $parentcat->get_all_children_ids();
foreach ($subcategoryids as $subcategoryid) {
$subcategory = static::get($subcategoryid);
if ($subcategory->has_capabilities($permissionstocheck)) {
return $subcategory;
}
}
return null;
}
}

View file

@ -1102,4 +1102,109 @@ class core_course_category_testcase extends advanced_testcase {
$this->assertCount(1, $courses);
$this->assertArrayHasKey($othercourse->id, $courses);
}
/**
* Test get_nearest_editable_subcategory() method.
*
* @covers \core_course_category::get_nearest_editable_subcategory
*/
public function test_get_nearest_editable_subcategory(): void {
global $DB;
$coursecreatorrole = $DB->get_record('role', ['shortname' => 'coursecreator']);
$managerrole = $DB->get_record('role', ['shortname' => 'manager']);
// Create categories.
$category1 = core_course_category::create(['name' => 'Cat1']);
$category2 = core_course_category::create(['name' => 'Cat2']);
$category3 = core_course_category::create(['name' => 'Cat3']);
// Get the category contexts.
$category1context = $category1->get_context();
$category2context = $category2->get_context();
$category3context = $category3->get_context();
// Create user.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
// Assign the user1 to 'Course creator' role for Cat1.
role_assign($coursecreatorrole->id, $user1->id, $category1context->id);
// Assign the user2 to 'Manager' role for Cat3.
role_assign($managerrole->id, $user2->id, $category3context->id);
// Start scenario 1.
// user3 has no permission to create course or manage category.
$this->setUser($user3);
$coursecat = core_course_category::user_top();
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['manage']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/category:manage']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create', 'manage']));
// End scenario 1.
// Start scenario 2.
// user1 has permission to create course but has no permission to manage category.
$this->setUser($user1);
$coursecat = core_course_category::user_top();
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['manage']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/category:manage']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create', 'manage']));
// The get_nearest_editable_subcategory should return Cat1.
$this->assertEquals($category1->id, core_course_category::get_nearest_editable_subcategory($coursecat, ['create'])->id);
$this->assertEquals($category1->id,
core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create'])->id);
// Assign the user1 to 'Course creator' role for Cat2.
role_assign($coursecreatorrole->id, $user1->id, $category2context->id);
// The get_nearest_editable_subcategory should still return Cat1 (First creatable subcategory) for create course capability.
$this->assertEquals($category1->id, core_course_category::get_nearest_editable_subcategory($coursecat, ['create'])->id);
$this->assertEquals($category1->id,
core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create'])->id);
// End scenario 2.
// Start scenario 3.
// user2 has no permission to create course but has permission to manage category.
$this->setUser($user2);
// Remove the moodle/course:create capability for the manager role.
unassign_capability('moodle/course:create', $managerrole->id);
$coursecat = core_course_category::user_top();
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['manage']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/category:manage']));
$this->assertEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create', 'manage']));
// The get_nearest_editable_subcategory should return Cat3.
$this->assertEquals($category3->id, core_course_category::get_nearest_editable_subcategory($coursecat, ['manage'])->id);
$this->assertEquals($category3->id,
core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/category:manage'])->id);
// End scenario 3.
// Start scenario 4.
// user2 has both permission to create course and manage category.
// Add the moodle/course:create capability back again for the manager role.
assign_capability('moodle/course:create', CAP_ALLOW, $managerrole->id, $category3context->id);
$this->setUser($user2);
$coursecat = core_course_category::user_top();
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/course:create']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['manage']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['moodle/category:manage']));
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, ['create', 'manage']));
// The get_nearest_editable_subcategory should return Cat3.
$this->assertEquals($category3->id,
core_course_category::get_nearest_editable_subcategory($coursecat, ['create', 'manage'])->id);
$this->assertEquals($category3->id, core_course_category::get_nearest_editable_subcategory($coursecat,
['moodle/course:create', 'moodle/category:manage'])->id);
// End scenario 4.
// Start scenario 5.
// Exception will be thrown if $permissionstocheck is empty.
$this->setUser($user1);
$coursecat = core_course_category::user_top();
$this->expectException('coding_exception');
$this->expectExceptionMessage('Invalid permissionstocheck parameter');
$this->assertNotEmpty(core_course_category::get_nearest_editable_subcategory($coursecat, []));
// End scenario 5.
}
}

View file

@ -87,6 +87,8 @@ course formats don't have their own renderer.
- print_course_request_buttons
* New page_setup() method in the core_course_category class. This method can be used for a general page setup in the course
category pages.
* New core_course_category::get_nearest_editable_subcategory():
- Return the core_course_category object for the first subcategory that the current user has the permission on it.
=== 3.11 ===
* A new callback xxx_coursemodule_definition_after_data that allows plugins to extend activity forms after the data is set.
@ -100,7 +102,6 @@ course formats don't have their own renderer.
- activity_dates_information_in_activity_should_not_exist()
- Given the activity date information in "<ActivityName>" should not exist
* A user preference usemodchooser has been removed and the activities/resources (non-ajax) activity chooser has been deprecated and will be removed in the future.
=== 3.10 ===
* The function make_categories_options() has now been deprecated. Please use \core_course_category::make_categories_list() instead.