mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
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:
parent
1d99ba19a2
commit
481cfdc3f0
6 changed files with 251 additions and 9 deletions
|
@ -3100,6 +3100,36 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||||
return course_request::can_request($this->get_context());
|
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.
|
* Returns true if the user can approve course requests.
|
||||||
* @return bool
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1102,4 +1102,109 @@ class core_course_category_testcase extends advanced_testcase {
|
||||||
$this->assertCount(1, $courses);
|
$this->assertCount(1, $courses);
|
||||||
$this->assertArrayHasKey($othercourse->id, $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.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,8 @@ course formats don't have their own renderer.
|
||||||
- print_course_request_buttons
|
- 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
|
* 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.
|
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 ===
|
=== 3.11 ===
|
||||||
* A new callback xxx_coursemodule_definition_after_data that allows plugins to extend activity forms after the data is set.
|
* 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()
|
- activity_dates_information_in_activity_should_not_exist()
|
||||||
- Given the activity date information in "<ActivityName>" 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.
|
* 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 ===
|
=== 3.10 ===
|
||||||
|
|
||||||
* The function make_categories_options() has now been deprecated. Please use \core_course_category::make_categories_list() instead.
|
* The function make_categories_options() has now been deprecated. Please use \core_course_category::make_categories_list() instead.
|
||||||
|
|
|
@ -63,12 +63,18 @@ $PAGE->theme->addblockposition = BLOCK_ADDBLOCK_POSITION_CUSTOM;
|
||||||
|
|
||||||
// Add course management if the user has the capabilities for it.
|
// Add course management if the user has the capabilities for it.
|
||||||
$coursecat = core_course_category::user_top();
|
$coursecat = core_course_category::user_top();
|
||||||
if ($coursecat->can_create_course() || $coursecat->has_manage_capability()) {
|
$coursemanagemenu = [];
|
||||||
$data = [
|
if ($category = core_course_category::get_nearest_editable_subcategory($coursecat, ['create'])) {
|
||||||
'newcourseurl' => new moodle_url('/course/edit.php', ['category' => $coursecat->id]),
|
// The user has the capability to create course.
|
||||||
'manageurl' => new moodle_url('/course/management.php', ['categoryid' => $coursecat->id]),
|
$coursemanagemenu['newcourseurl'] = new moodle_url('/course/edit.php', ['category' => $category->id]);
|
||||||
];
|
}
|
||||||
$PAGE->add_header_action($OUTPUT->render_from_template('my/dropdown', $data));
|
if ($category = core_course_category::get_nearest_editable_subcategory($coursecat, ['manage'])) {
|
||||||
|
// The user has the capability to manage the course category.
|
||||||
|
$coursemanagemenu['manageurl'] = new moodle_url('/course/management.php', ['categoryid' => $category->id]);
|
||||||
|
}
|
||||||
|
if (!empty($coursemanagemenu)) {
|
||||||
|
// Render the course management menu.
|
||||||
|
$PAGE->add_header_action($OUTPUT->render_from_template('my/dropdown', $coursemanagemenu));
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $OUTPUT->header();
|
echo $OUTPUT->header();
|
||||||
|
|
|
@ -31,7 +31,11 @@
|
||||||
<i class="fa fa-ellipsis-v text-dark py-2" aria-hidden="true"></i>
|
<i class="fa fa-ellipsis-v text-dark py-2" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
{{#newcourseurl}}
|
||||||
<a class="dropdown-item" href="{{newcourseurl}}">{{#str}}newcourse, core{{/str}}</a>
|
<a class="dropdown-item" href="{{newcourseurl}}">{{#str}}newcourse, core{{/str}}</a>
|
||||||
|
{{/newcourseurl}}
|
||||||
|
{{#manageurl}}
|
||||||
<a class="dropdown-item" href="{{manageurl}}">{{#str}}managecourses, core{{/str}}</a>
|
<a class="dropdown-item" href="{{manageurl}}">{{#str}}managecourses, core{{/str}}</a>
|
||||||
|
{{/manageurl}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
@core @core_my
|
@core @core_my
|
||||||
Feature: Run tests over my courses.
|
Feature: Run tests over my courses.
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname | email |
|
||||||
|
| user1 | User | 1 | user1@example.com |
|
||||||
|
And the following "categories" exist:
|
||||||
|
| name | category | idnumber |
|
||||||
|
| CatA | 0 | cata |
|
||||||
|
And the following "roles" exist:
|
||||||
|
| shortname | name | archetype |
|
||||||
|
| role1 | Role 1 | |
|
||||||
|
And the following "system role assigns" exist:
|
||||||
|
| user | role | contextlevel | reference |
|
||||||
|
| user1 | role1 | Category | CatA |
|
||||||
|
|
||||||
Scenario: Admin can add new courses or manage them from my courses
|
Scenario: Admin can add new courses or manage them from my courses
|
||||||
Given I am on the "My courses" page logged in as "admin"
|
Given I am on the "My courses" page logged in as "admin"
|
||||||
And I click on "Course management options" "link"
|
And I click on "Course management options" "link"
|
||||||
|
@ -11,3 +25,57 @@ Feature: Run tests over my courses.
|
||||||
And I click on "Course management options" "link"
|
And I click on "Course management options" "link"
|
||||||
And I click on "Manage courses" "link"
|
And I click on "Manage courses" "link"
|
||||||
And I should see "Manage course categories and courses"
|
And I should see "Manage course categories and courses"
|
||||||
|
|
||||||
|
Scenario: User without creating a course and managing category permissions cannot see any link
|
||||||
|
Given I am on the "My courses" page logged in as "user1"
|
||||||
|
Then "Course management options" "link" should not exist
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: User with creating a course permission can see the Create course link only
|
||||||
|
Given the following "permission overrides" exist:
|
||||||
|
| capability | permission | role | contextlevel | reference |
|
||||||
|
| moodle/course:create | Allow | role1 | Category | cata |
|
||||||
|
When I am on the "My courses" page logged in as "user1"
|
||||||
|
Then "Course management options" "link" should exist
|
||||||
|
And I click on "Course management options" "link"
|
||||||
|
And I should see "New course"
|
||||||
|
And I should not see "Manage courses"
|
||||||
|
And I click on "New course" "link"
|
||||||
|
And I wait to be redirected
|
||||||
|
And I should see "Add a new course"
|
||||||
|
And "CatA" "autocomplete_selection" should exist
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: User with managing a category permission can see the Manage course link only
|
||||||
|
Given the following "permission overrides" exist:
|
||||||
|
| capability | permission | role | contextlevel | reference |
|
||||||
|
| moodle/category:manage | Allow | role1 | Category | cata |
|
||||||
|
When I am on the "My courses" page logged in as "user1"
|
||||||
|
Then "Course management options" "link" should exist
|
||||||
|
And I click on "Course management options" "link"
|
||||||
|
And I should not see "New course"
|
||||||
|
And I should see "Manage courses"
|
||||||
|
And I click on "Manage courses" "link"
|
||||||
|
And I wait to be redirected
|
||||||
|
And I should see "Manage course categories and courses"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: User with both creating a course and managing a category permission can see both links
|
||||||
|
Given the following "permission overrides" exist:
|
||||||
|
| capability | permission | role | contextlevel | reference |
|
||||||
|
| moodle/course:create | Allow | role1 | Category | cata |
|
||||||
|
| moodle/category:manage | Allow | role1 | Category | cata |
|
||||||
|
When I am on the "My courses" page logged in as "user1"
|
||||||
|
Then "Course management options" "link" should exist
|
||||||
|
And I click on "Course management options" "link"
|
||||||
|
And I should see "New course"
|
||||||
|
And I should see "Manage courses"
|
||||||
|
And I click on "New course" "link"
|
||||||
|
And I wait to be redirected
|
||||||
|
And I should see "Add a new course"
|
||||||
|
And "CatA" "autocomplete_selection" should exist
|
||||||
|
And I am on the "My courses" page
|
||||||
|
And I click on "Course management options" "link"
|
||||||
|
And I click on "Manage courses" "link"
|
||||||
|
And I wait to be redirected
|
||||||
|
And I should see "Manage course categories and courses"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue