mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00
Merge branch 'MDL-72099-master' of https://github.com/aanabit/moodle
This commit is contained in:
commit
e95aee50cc
13 changed files with 511 additions and 86 deletions
|
@ -224,6 +224,37 @@ class contentbank {
|
||||||
return $contents;
|
return $contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the context where a user has all the given capabilities.
|
||||||
|
*
|
||||||
|
* @param string $capability The capability the user needs to have.
|
||||||
|
* @param int|null $userid Optional userid. $USER by default.
|
||||||
|
* @return array Array of the courses and course categories where the user has the given capability.
|
||||||
|
*/
|
||||||
|
public function get_contexts_with_capabilities_by_user($capability = 'moodle/contentbank:access', $userid = null): array {
|
||||||
|
global $USER;
|
||||||
|
|
||||||
|
if (!$userid) {
|
||||||
|
$userid = $USER->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoriescache = \cache::make('core', 'contentbank_allowed_categories');
|
||||||
|
$coursescache = \cache::make('core', 'contentbank_allowed_courses');
|
||||||
|
|
||||||
|
$categories = $categoriescache->get($userid);
|
||||||
|
$courses = $coursescache->get($userid);
|
||||||
|
|
||||||
|
if ($categories === false || $courses === false) {
|
||||||
|
list($categories, $courses) = get_user_capability_contexts($capability, true, $userid, true,
|
||||||
|
'shortname, ctxlevel, ctxinstance, ctxid', 'name, ctxlevel, ctxinstance, ctxid', 'shortname', 'name');
|
||||||
|
$categoriescache->set($userid, $categories);
|
||||||
|
$coursescache->set($userid, $courses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$categories, $courses];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create content from a file information.
|
* Create content from a file information.
|
||||||
*
|
*
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
namespace core_contentbank\output;
|
namespace core_contentbank\output;
|
||||||
|
|
||||||
|
use core_contentbank\contentbank;
|
||||||
use renderable;
|
use renderable;
|
||||||
use templatable;
|
use templatable;
|
||||||
use renderer_base;
|
use renderer_base;
|
||||||
|
@ -53,17 +54,29 @@ class bankcontent implements renderable, templatable {
|
||||||
*/
|
*/
|
||||||
private $context;
|
private $context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Course categories that the user has access to.
|
||||||
|
*/
|
||||||
|
private $allowedcategories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Courses that the user has access to.
|
||||||
|
*/
|
||||||
|
private $allowedcourses;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct this renderable.
|
* Construct this renderable.
|
||||||
*
|
*
|
||||||
* @param \core_contentbank\content[] $contents Array of content bank contents.
|
* @param \core_contentbank\content[] $contents Array of content bank contents.
|
||||||
* @param array $toolbar List of content bank toolbar options.
|
* @param array $toolbar List of content bank toolbar options.
|
||||||
* @param \context $context Optional context to check (default null)
|
* @param \context $context Optional context to check (default null)
|
||||||
|
* @param contentbank $cb Contenbank object.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $contents, array $toolbar, \context $context = null) {
|
public function __construct(array $contents, array $toolbar, \context $context = null, contentbank $cb) {
|
||||||
$this->contents = $contents;
|
$this->contents = $contents;
|
||||||
$this->toolbar = $toolbar;
|
$this->toolbar = $toolbar;
|
||||||
$this->context = $context;
|
$this->context = $context;
|
||||||
|
list($this->allowedcategories, $this->allowedcourses) = $cb->get_contexts_with_capabilities_by_user();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +86,7 @@ class bankcontent implements renderable, templatable {
|
||||||
* @return stdClass
|
* @return stdClass
|
||||||
*/
|
*/
|
||||||
public function export_for_template(renderer_base $output): stdClass {
|
public function export_for_template(renderer_base $output): stdClass {
|
||||||
global $PAGE;
|
global $PAGE, $SITE;
|
||||||
|
|
||||||
$PAGE->requires->js_call_amd('core_contentbank/search', 'init');
|
$PAGE->requires->js_call_amd('core_contentbank/search', 'init');
|
||||||
$PAGE->requires->js_call_amd('core_contentbank/sort', 'init');
|
$PAGE->requires->js_call_amd('core_contentbank/sort', 'init');
|
||||||
|
@ -118,6 +131,40 @@ class bankcontent implements renderable, templatable {
|
||||||
$data->tools[] = $tool;
|
$data->tools[] = $tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allowedcontexts = [];
|
||||||
|
$systemcontext = \context_system::instance();
|
||||||
|
if (has_capability('moodle/contentbank:access', $systemcontext)) {
|
||||||
|
$allowedcontexts[$systemcontext->id] = get_string('coresystem');
|
||||||
|
}
|
||||||
|
$options = [];
|
||||||
|
foreach ($this->allowedcategories as $allowedcategory) {
|
||||||
|
$options[$allowedcategory->ctxid] = $allowedcategory->name;
|
||||||
|
}
|
||||||
|
if (!empty($options)) {
|
||||||
|
$allowedcontexts['categories'] = [get_string('coursecategories') => $options];
|
||||||
|
}
|
||||||
|
$options = [];
|
||||||
|
foreach ($this->allowedcourses as $allowedcourse) {
|
||||||
|
// Don't add the frontpage course to the list.
|
||||||
|
if ($allowedcourse->id != $SITE->id) {
|
||||||
|
$options[$allowedcourse->ctxid] = $allowedcourse->shortname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($options)) {
|
||||||
|
$allowedcontexts['courses'] = [get_string('courses') => $options];
|
||||||
|
}
|
||||||
|
if (!empty($allowedcontexts)) {
|
||||||
|
$url = new \moodle_url('/contentbank/index.php');
|
||||||
|
$singleselect = new \single_select(
|
||||||
|
$url,
|
||||||
|
'contextid',
|
||||||
|
$allowedcontexts,
|
||||||
|
$this->context->id,
|
||||||
|
get_string('choosecontext', 'core_contentbank')
|
||||||
|
);
|
||||||
|
$data->allowedcontexts = $singleselect->export_for_template($output);
|
||||||
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ if ($errormsg !== '' && get_string_manager()->string_exists($errormsg, 'core_con
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the contentbank contents.
|
// Render the contentbank contents.
|
||||||
$folder = new \core_contentbank\output\bankcontent($foldercontents, $toolbar, $context);
|
$folder = new \core_contentbank\output\bankcontent($foldercontents, $toolbar, $context, $cb);
|
||||||
echo $OUTPUT->render($folder);
|
echo $OUTPUT->render($folder);
|
||||||
|
|
||||||
echo $OUTPUT->box_end();
|
echo $OUTPUT->box_end();
|
||||||
|
|
|
@ -75,15 +75,41 @@
|
||||||
{
|
{
|
||||||
"icon": "i/export"
|
"icon": "i/export"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"allowedcontexts": [
|
||||||
|
{
|
||||||
|
"name": "contextid",
|
||||||
|
"method": "get",
|
||||||
|
"action": "http://localhost/stable_master/contentbank/index.php",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "1",
|
||||||
|
"name": "System",
|
||||||
|
"selected": true,
|
||||||
|
"optgroup": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "32",
|
||||||
|
"name": "Category 1",
|
||||||
|
"selected": false,
|
||||||
|
"optgroup": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<div class="content-bank-container {{#viewlist}}view-list{{/viewlist}} {{^viewlist}}view-grid{{/viewlist}}"
|
<div class="content-bank-container {{#viewlist}}view-list{{/viewlist}} {{^viewlist}}view-grid{{/viewlist}}"
|
||||||
data-region="contentbank">
|
data-region="contentbank">
|
||||||
<div class="d-flex justify-content-between flex-column flex-sm-row">
|
<div class="d-flex justify-content-between flex-column flex-sm-row">
|
||||||
<div class="cb-search-container mb-2">
|
<div class="d-flex">
|
||||||
{{>core_contentbank/bankcontent/search}}
|
<div class="cb-navigation-container mb-2 mr-2">
|
||||||
|
{{>core_contentbank/bankcontent/navigation}}
|
||||||
|
</div>
|
||||||
|
<div class="cb-search-container mb-2">
|
||||||
|
{{>core_contentbank/bankcontent/search}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cb-toolbar-container mb-2 d-flex">
|
<div class="cb-toolbar-container mb-2 d-flex">
|
||||||
{{>core_contentbank/bankcontent/toolbar}}
|
{{>core_contentbank/bankcontent/toolbar}}
|
||||||
|
|
27
contentbank/templates/bankcontent/navigation.mustache
Normal file
27
contentbank/templates/bankcontent/navigation.mustache
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{{!
|
||||||
|
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/>.
|
||||||
|
}}
|
||||||
|
{{!
|
||||||
|
@template core_contentbank/bankcontent/navigation
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{#allowedcontexts}}
|
||||||
|
{{> core/single_select }}
|
||||||
|
{{/allowedcontexts}}
|
104
contentbank/tests/behat/navigate_content.feature
Normal file
104
contentbank/tests/behat/navigate_content.feature
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript
|
||||||
|
Feature: Navigate to different contexts in the content bank
|
||||||
|
In order to navigate easily in the content bank
|
||||||
|
I need to be able to view dropdown with all allowed contexts in the content bank
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given I log in as "admin"
|
||||||
|
And the following "categories" exist:
|
||||||
|
| name | category | idnumber |
|
||||||
|
| Cat 1 | 0 | CAT1 |
|
||||||
|
| Cat 2 | 0 | CAT2 |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname | category |
|
||||||
|
| Course 0 | C0 | |
|
||||||
|
| Course 1 | C1 | CAT1 |
|
||||||
|
| Course 2 | C2 | CAT2 |
|
||||||
|
And I navigate to "H5P > Manage H5P content types" in site administration
|
||||||
|
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
|
||||||
|
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
|
||||||
|
And the following "contentbank content" exist:
|
||||||
|
| contextlevel | reference | contenttype | user | contentname | filepath |
|
||||||
|
| System | | contenttype_h5p | admin | santjordi.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
| Category | CAT1 | contenttype_h5p | admin | santjordi_rose.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
| Category | CAT2 | contenttype_h5p | admin | SantJordi_book | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
| Course | C0 | contenttype_h5p | admin | Dragon.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
| Course | C1 | contenttype_h5p | admin | princess.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
| Course | C2 | contenttype_h5p | admin | mathsbook.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||||
|
|
||||||
|
Scenario: Admins can view and navigate to all the contexts in the content bank
|
||||||
|
Given I am on site homepage
|
||||||
|
And I turn editing mode on
|
||||||
|
And I add the "Navigation" block if not present
|
||||||
|
And I expand "Site pages" node
|
||||||
|
When I click on "Content bank" "link"
|
||||||
|
And "contextid" "select" should exist
|
||||||
|
And the "contextid" select box should contain "System"
|
||||||
|
And the "contextid" select box should contain "Cat 1"
|
||||||
|
And the "contextid" select box should contain "Cat 2"
|
||||||
|
And the "contextid" select box should contain "C0"
|
||||||
|
And the "contextid" select box should contain "C1"
|
||||||
|
And the "contextid" select box should contain "C2"
|
||||||
|
And I should see "santjordi.h5p"
|
||||||
|
And I should not see "santjordi_rose.h5p"
|
||||||
|
And I should not see "Dragon.h5p"
|
||||||
|
And I click on "contextid" "select"
|
||||||
|
And I click on "Cat 1" "option"
|
||||||
|
Then I should not see "santjordi.h5p"
|
||||||
|
And I should see "santjordi_rose.h5p"
|
||||||
|
And I should not see "Dragon.h5p"
|
||||||
|
And I click on "contextid" "select"
|
||||||
|
And I click on "C0" "option"
|
||||||
|
And I should not see "santjordi.h5p"
|
||||||
|
And I should not see "santjordi_rose.h5p"
|
||||||
|
And I should see "Dragon.h5p"
|
||||||
|
|
||||||
|
Scenario: Teachers can view and navigate to contexts in the content bank based on their permissions
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname |
|
||||||
|
| teacher | Joseba | Cilarte |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| teacher | C0 | editingteacher |
|
||||||
|
| teacher | C1 | editingteacher |
|
||||||
|
And I log out
|
||||||
|
And I am on the "C0" "Course" page logged in as "teacher"
|
||||||
|
And I turn editing mode on
|
||||||
|
And I add the "Navigation" block if not present
|
||||||
|
And I expand "Site pages" node
|
||||||
|
When I click on "Content bank" "link"
|
||||||
|
And "contextid" "select" should exist
|
||||||
|
And the "contextid" select box should contain "C0"
|
||||||
|
And the "contextid" select box should contain "C1"
|
||||||
|
And the "contextid" select box should not contain "System"
|
||||||
|
And the "contextid" select box should not contain "Cat 1"
|
||||||
|
And the "contextid" select box should not contain "Cat 2"
|
||||||
|
And the "contextid" select box should not contain "C2"
|
||||||
|
And I should see "Dragon.h5p"
|
||||||
|
And I should not see "princess.h5p"
|
||||||
|
And I should not see "santjordi.h5p"
|
||||||
|
And I should not see "santjordi_rose.h5p"
|
||||||
|
And I click on "contextid" "select"
|
||||||
|
And I click on "C1" "option"
|
||||||
|
Then I should not see "Dragon.h5p"
|
||||||
|
And I should see "princess.h5p"
|
||||||
|
And I should not see "santjordi.h5p"
|
||||||
|
And I should not see "santjordi_rose.h5p"
|
||||||
|
And the following "role assigns" exist:
|
||||||
|
| user | role | contextlevel | reference |
|
||||||
|
| teacher | manager | Category | CAT1 |
|
||||||
|
And I am on the "C0" "Course" page logged in as "teacher"
|
||||||
|
And I expand "Site pages" node
|
||||||
|
When I click on "Content bank" "link"
|
||||||
|
And "contextid" "select" should exist
|
||||||
|
And the "contextid" select box should contain "C0"
|
||||||
|
And the "contextid" select box should contain "C1"
|
||||||
|
And the "contextid" select box should contain "Cat 1"
|
||||||
|
And the "contextid" select box should not contain "System"
|
||||||
|
And the "contextid" select box should not contain "Cat 2"
|
||||||
|
And the "contextid" select box should not contain "C2"
|
||||||
|
And I should see "Dragon.h5p"
|
||||||
|
And I click on "contextid" "select"
|
||||||
|
And I click on "Cat 1" "option"
|
||||||
|
And I should not see "Dragon.h5p"
|
||||||
|
And I should see "santjordi_rose.h5p"
|
|
@ -41,6 +41,8 @@ $string['cachedef_calendar_subscriptions'] = 'Calendar subscriptions';
|
||||||
$string['cachedef_calendar_categories'] = 'Calendar course categories that a user can access';
|
$string['cachedef_calendar_categories'] = 'Calendar course categories that a user can access';
|
||||||
$string['cachedef_capabilities'] = 'System capabilities list';
|
$string['cachedef_capabilities'] = 'System capabilities list';
|
||||||
$string['cachedef_config'] = 'Config settings';
|
$string['cachedef_config'] = 'Config settings';
|
||||||
|
$string['cachedef_contentbank_allowed_categories'] = 'Allowed content bank course categories for current user';
|
||||||
|
$string['cachedef_contentbank_allowed_courses'] = 'Allowed content bank courses for current user';
|
||||||
$string['cachedef_contentbank_enabled_extensions'] = 'Allowed extensions and its supporter plugins in content bank';
|
$string['cachedef_contentbank_enabled_extensions'] = 'Allowed extensions and its supporter plugins in content bank';
|
||||||
$string['cachedef_contentbank_context_extensions'] = 'Allowed extensions and its supporter plugins in a content bank context';
|
$string['cachedef_contentbank_context_extensions'] = 'Allowed extensions and its supporter plugins in a content bank context';
|
||||||
$string['cachedef_coursecat'] = 'Course categories lists for particular user';
|
$string['cachedef_coursecat'] = 'Course categories lists for particular user';
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
$string['author'] = 'Author';
|
$string['author'] = 'Author';
|
||||||
$string['contentbank'] = 'Content bank';
|
$string['contentbank'] = 'Content bank';
|
||||||
$string['close'] = 'Close';
|
$string['close'] = 'Close';
|
||||||
|
$string['choosecontext'] = 'Choose course or category...';
|
||||||
$string['contentbankpreferences'] = 'Content bank preferences';
|
$string['contentbankpreferences'] = 'Content bank preferences';
|
||||||
$string['contentdeleted'] = 'The content has been deleted.';
|
$string['contentdeleted'] = 'The content has been deleted.';
|
||||||
$string['contentname'] = 'Content name';
|
$string['contentname'] = 'Content name';
|
||||||
|
|
|
@ -4101,6 +4101,116 @@ function count_role_users($roleid, context $context, $parent = false) {
|
||||||
return $DB->count_records_sql($sql, $params);
|
return $DB->count_records_sql($sql, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function gets the list of course and course category contexts that this user has a particular capability in.
|
||||||
|
*
|
||||||
|
* It is now reasonably efficient, but bear in mind that if there are users who have the capability
|
||||||
|
* everywhere, it may return an array of all contexts.
|
||||||
|
*
|
||||||
|
* @param string $capability Capability in question
|
||||||
|
* @param int $userid User ID or null for current user
|
||||||
|
* @param bool $getcategories Wether to return also course_categories
|
||||||
|
* @param bool $doanything True if 'doanything' is permitted (default)
|
||||||
|
* @param string $coursefieldsexceptid Leave blank if you only need 'id' in the course records;
|
||||||
|
* otherwise use a comma-separated list of the fields you require, not including id.
|
||||||
|
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||||
|
* @param string $categoryfieldsexceptid Leave blank if you only need 'id' in the course records;
|
||||||
|
* otherwise use a comma-separated list of the fields you require, not including id.
|
||||||
|
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||||
|
* @param string $courseorderby If set, use a comma-separated list of fields from course
|
||||||
|
* table with sql modifiers (DESC) if needed
|
||||||
|
* @param string $categoryorderby If set, use a comma-separated list of fields from course_category
|
||||||
|
* table with sql modifiers (DESC) if needed
|
||||||
|
* @param int $limit Limit the number of courses to return on success. Zero equals all entries.
|
||||||
|
* @return array Array of categories and courses.
|
||||||
|
*/
|
||||||
|
function get_user_capability_contexts(string $capability, bool $getcategories, $userid = null, $doanything = true,
|
||||||
|
$coursefieldsexceptid = '', $categoryfieldsexceptid = '', $courseorderby = '',
|
||||||
|
$categoryorderby = '', $limit = 0): array {
|
||||||
|
global $DB, $USER;
|
||||||
|
|
||||||
|
// Default to current user.
|
||||||
|
if (!$userid) {
|
||||||
|
$userid = $USER->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($doanything && is_siteadmin($userid)) {
|
||||||
|
// If the user is a site admin and $doanything is enabled then there is no need to restrict
|
||||||
|
// the list of courses.
|
||||||
|
$contextlimitsql = '';
|
||||||
|
$contextlimitparams = [];
|
||||||
|
} else {
|
||||||
|
// Gets SQL to limit contexts ('x' table) to those where the user has this capability.
|
||||||
|
list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
|
||||||
|
$userid, $capability);
|
||||||
|
if (!$contextlimitsql) {
|
||||||
|
// If the does not have this capability in any context, return false without querying.
|
||||||
|
return [false, false];
|
||||||
|
}
|
||||||
|
|
||||||
|
$contextlimitsql = 'WHERE' . $contextlimitsql;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories = [];
|
||||||
|
if ($getcategories) {
|
||||||
|
$fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($categoryfieldsexceptid);
|
||||||
|
if ($categoryorderby) {
|
||||||
|
$fields = explode(',', $categoryorderby);
|
||||||
|
$orderby = '';
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if ($orderby) {
|
||||||
|
$orderby .= ',';
|
||||||
|
}
|
||||||
|
$orderby .= 'c.'.$field;
|
||||||
|
}
|
||||||
|
$orderby = 'ORDER BY '.$orderby;
|
||||||
|
}
|
||||||
|
$rs = $DB->get_recordset_sql("
|
||||||
|
SELECT c.id $fieldlist
|
||||||
|
FROM {course_categories} c
|
||||||
|
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
||||||
|
$contextlimitsql
|
||||||
|
$orderby", array_merge([CONTEXT_COURSECAT], $contextlimitparams));
|
||||||
|
$basedlimit = $limit;
|
||||||
|
foreach ($rs as $category) {
|
||||||
|
$categories[] = $category;
|
||||||
|
$basedlimit--;
|
||||||
|
if ($basedlimit == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$courses = [];
|
||||||
|
$fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($coursefieldsexceptid);
|
||||||
|
if ($courseorderby) {
|
||||||
|
$fields = explode(',', $courseorderby);
|
||||||
|
$courseorderby = '';
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if ($courseorderby) {
|
||||||
|
$courseorderby .= ',';
|
||||||
|
}
|
||||||
|
$courseorderby .= 'c.'.$field;
|
||||||
|
}
|
||||||
|
$courseorderby = 'ORDER BY '.$courseorderby;
|
||||||
|
}
|
||||||
|
$rs = $DB->get_recordset_sql("
|
||||||
|
SELECT c.id $fieldlist
|
||||||
|
FROM {course} c
|
||||||
|
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
||||||
|
$contextlimitsql
|
||||||
|
$courseorderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
|
||||||
|
foreach ($rs as $course) {
|
||||||
|
$courses[] = $course;
|
||||||
|
$limit--;
|
||||||
|
if ($limit == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rs->close();
|
||||||
|
return [$categories, $courses];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function gets the list of courses that this user has a particular capability in.
|
* This function gets the list of courses that this user has a particular capability in.
|
||||||
*
|
*
|
||||||
|
@ -4118,84 +4228,20 @@ function count_role_users($roleid, context $context, $parent = false) {
|
||||||
* @param int $limit Limit the number of courses to return on success. Zero equals all entries.
|
* @param int $limit Limit the number of courses to return on success. Zero equals all entries.
|
||||||
* @return array|bool Array of courses, if none found false is returned.
|
* @return array|bool Array of courses, if none found false is returned.
|
||||||
*/
|
*/
|
||||||
function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '',
|
function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '',
|
||||||
$limit = 0) {
|
$orderby = '', $limit = 0) {
|
||||||
global $DB, $USER;
|
list($categories, $courses) = get_user_capability_contexts(
|
||||||
|
$capability,
|
||||||
// Default to current user.
|
false,
|
||||||
if (!$userid) {
|
$userid,
|
||||||
$userid = $USER->id;
|
$doanything,
|
||||||
}
|
$fieldsexceptid,
|
||||||
|
'',
|
||||||
if ($doanything && is_siteadmin($userid)) {
|
$orderby,
|
||||||
// If the user is a site admin and $doanything is enabled then there is no need to restrict
|
'',
|
||||||
// the list of courses.
|
$limit
|
||||||
$contextlimitsql = '';
|
);
|
||||||
$contextlimitparams = [];
|
return $courses;
|
||||||
} else {
|
|
||||||
// Gets SQL to limit contexts ('x' table) to those where the user has this capability.
|
|
||||||
list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
|
|
||||||
$userid, $capability);
|
|
||||||
if (!$contextlimitsql) {
|
|
||||||
// If the does not have this capability in any context, return false without querying.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contextlimitsql = 'WHERE' . $contextlimitsql;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert fields list and ordering
|
|
||||||
$fieldlist = '';
|
|
||||||
if ($fieldsexceptid) {
|
|
||||||
$fields = array_map('trim', explode(',', $fieldsexceptid));
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
// Context fields have a different alias.
|
|
||||||
if (strpos($field, 'ctx') === 0) {
|
|
||||||
switch($field) {
|
|
||||||
case 'ctxlevel' :
|
|
||||||
$realfield = 'contextlevel';
|
|
||||||
break;
|
|
||||||
case 'ctxinstance' :
|
|
||||||
$realfield = 'instanceid';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$realfield = substr($field, 3);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$fieldlist .= ',x.' . $realfield . ' AS ' . $field;
|
|
||||||
} else {
|
|
||||||
$fieldlist .= ',c.'.$field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($orderby) {
|
|
||||||
$fields = explode(',', $orderby);
|
|
||||||
$orderby = '';
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
if ($orderby) {
|
|
||||||
$orderby .= ',';
|
|
||||||
}
|
|
||||||
$orderby .= 'c.'.$field;
|
|
||||||
}
|
|
||||||
$orderby = 'ORDER BY '.$orderby;
|
|
||||||
}
|
|
||||||
|
|
||||||
$courses = array();
|
|
||||||
$rs = $DB->get_recordset_sql("
|
|
||||||
SELECT c.id $fieldlist
|
|
||||||
FROM {course} c
|
|
||||||
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
|
||||||
$contextlimitsql
|
|
||||||
$orderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
|
|
||||||
foreach ($rs as $course) {
|
|
||||||
$courses[] = $course;
|
|
||||||
$limit--;
|
|
||||||
if ($limit == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$rs->close();
|
|
||||||
return empty($courses) ? false : $courses;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -429,4 +429,39 @@ class get_user_capability_course_helper {
|
||||||
return self::create_sql($root);
|
return self::create_sql($root);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map fieldnames to get ready for the SQL query.
|
||||||
|
*
|
||||||
|
* @param string $fieldsexceptid A comma-separated list of the fields you require, not including id.
|
||||||
|
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||||
|
* @return string Mapped field list for the SQL query.
|
||||||
|
*/
|
||||||
|
public static function map_fieldnames(string $fieldsexceptid = ''): string {
|
||||||
|
// Convert fields list and ordering.
|
||||||
|
$fieldlist = '';
|
||||||
|
if ($fieldsexceptid) {
|
||||||
|
$fields = array_map('trim', explode(',', $fieldsexceptid));
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
// Context fields have a different alias.
|
||||||
|
if (strpos($field, 'ctx') === 0) {
|
||||||
|
switch($field) {
|
||||||
|
case 'ctxlevel' :
|
||||||
|
$realfield = 'contextlevel';
|
||||||
|
break;
|
||||||
|
case 'ctxinstance' :
|
||||||
|
$realfield = 'instanceid';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$realfield = substr($field, 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$fieldlist .= ',x.' . $realfield . ' AS ' . $field;
|
||||||
|
} else {
|
||||||
|
$fieldlist .= ',c.'.$field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $fieldlist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,4 +493,27 @@ $definitions = array(
|
||||||
'staticacceleration' => true,
|
'staticacceleration' => true,
|
||||||
'datasource' => '\core_course\cache\course_image',
|
'datasource' => '\core_course\cache\course_image',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Cache the course categories where the user has access the content bank.
|
||||||
|
'contentbank_allowed_categories' => [
|
||||||
|
'mode' => cache_store::MODE_SESSION,
|
||||||
|
'simplekeys' => true,
|
||||||
|
'simpledata' => true,
|
||||||
|
'invalidationevents' => [
|
||||||
|
'changesincoursecat',
|
||||||
|
'changesincategoryenrolment',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Cache the courses where the user has access the content bank.
|
||||||
|
'contentbank_allowed_courses' => [
|
||||||
|
'mode' => cache_store::MODE_SESSION,
|
||||||
|
'simplekeys' => true,
|
||||||
|
'simpledata' => true,
|
||||||
|
'invalidationevents' => [
|
||||||
|
'changesincoursecat',
|
||||||
|
'changesincategoryenrolment',
|
||||||
|
'changesincourse',
|
||||||
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -2230,6 +2230,89 @@ class core_accesslib_testcase extends advanced_testcase {
|
||||||
$this->assert_course_ids([SITEID, $c1->id, $c2->id], $courses);
|
$this->assert_course_ids([SITEID, $c1->id, $c2->id], $courses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests get_user_capability_contexts() which checks a capability across all courses and categories.
|
||||||
|
* Testing for categories only because courses results are covered by test_get_user_capability_course.
|
||||||
|
*/
|
||||||
|
public function test_get_user_capability_contexts() {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$cap = 'moodle/contentbank:access';
|
||||||
|
$defaultcategoryid = 1;
|
||||||
|
|
||||||
|
// The structure being created here is this:
|
||||||
|
//
|
||||||
|
// All tests work with the single capability 'moodle/contentbank:access'.
|
||||||
|
// ROLE DEF/OVERRIDE .
|
||||||
|
// Role: Allow Prohibit Empty .
|
||||||
|
// System ALLOW PROHIBIT .
|
||||||
|
// cat1 PREVENT ALLOW ALLOW .
|
||||||
|
// cat3 ALLOW PROHIBIT .
|
||||||
|
// cat2 PROHIBIT PROHIBIT PROHIBIT .
|
||||||
|
|
||||||
|
// Create a role which allows contentbank:access and one that prohibits it, and one neither.
|
||||||
|
$allowroleid = $generator->create_role();
|
||||||
|
$prohibitroleid = $generator->create_role();
|
||||||
|
$emptyroleid = $generator->create_role();
|
||||||
|
$systemcontext = context_system::instance();
|
||||||
|
assign_capability($cap, CAP_ALLOW, $allowroleid, $systemcontext->id);
|
||||||
|
assign_capability($cap, CAP_PROHIBIT, $prohibitroleid, $systemcontext->id);
|
||||||
|
|
||||||
|
// Create three categories (two of them nested).
|
||||||
|
$cat1 = $generator->create_category();
|
||||||
|
$cat2 = $generator->create_category();
|
||||||
|
$cat3 = $generator->create_category(['parent' => $cat1->id]);
|
||||||
|
|
||||||
|
// Category overrides: in cat 1, empty role is allowed; in cat 2, empty role is prevented.
|
||||||
|
assign_capability($cap, CAP_ALLOW, $emptyroleid,
|
||||||
|
context_coursecat::instance($cat1->id)->id);
|
||||||
|
assign_capability($cap, CAP_PREVENT, $emptyroleid,
|
||||||
|
context_coursecat::instance($cat2->id)->id);
|
||||||
|
|
||||||
|
// Course category overrides: in cat1, allow role is prevented and prohibit role is allowed;
|
||||||
|
// in Cat2, allow role is prohibited.
|
||||||
|
assign_capability($cap, CAP_PREVENT, $allowroleid,
|
||||||
|
context_coursecat::instance($cat1->id)->id);
|
||||||
|
assign_capability($cap, CAP_ALLOW, $prohibitroleid,
|
||||||
|
context_coursecat::instance($cat1->id)->id);
|
||||||
|
assign_capability($cap, CAP_PROHIBIT, $allowroleid,
|
||||||
|
context_coursecat::instance($cat2->id)->id);
|
||||||
|
|
||||||
|
// User 1 has no roles except default user role.
|
||||||
|
$u1 = $generator->create_user();
|
||||||
|
|
||||||
|
// It returns false (annoyingly) if there are no course categories.
|
||||||
|
list($categories, $courses) = get_user_capability_contexts($cap, true, $u1->id, true, '', '', '', 'id');
|
||||||
|
$this->assertFalse($categories);
|
||||||
|
|
||||||
|
// User 2 has allow role (system wide).
|
||||||
|
$u2 = $generator->create_user();
|
||||||
|
role_assign($allowroleid, $u2->id, $systemcontext->id);
|
||||||
|
|
||||||
|
// Should get $defaultcategory only. cat2 is prohibited; cat1 is prevented, so cat3 is not allowed.
|
||||||
|
list($categories, $courses) = get_user_capability_contexts($cap, true, $u2->id, true, '', '', '', 'id');
|
||||||
|
// Using same assert_course_ids helper even when we are checking course category ids.
|
||||||
|
$this->assert_course_ids([$defaultcategoryid], $categories);
|
||||||
|
|
||||||
|
// User 3 has empty role (system wide).
|
||||||
|
$u3 = $generator->create_user();
|
||||||
|
role_assign($emptyroleid, $u3->id, $systemcontext->id);
|
||||||
|
|
||||||
|
// Should get cat1 and cat3. cat2 is prohibited; no access to system level.
|
||||||
|
list($categories, $courses) = get_user_capability_contexts($cap, true, $u3->id, true, '', '', '', 'id');
|
||||||
|
$this->assert_course_ids([$cat1->id, $cat3->id], $categories);
|
||||||
|
|
||||||
|
// User 4 has prohibit role (system wide).
|
||||||
|
$u4 = $generator->create_user();
|
||||||
|
role_assign($prohibitroleid, $u4->id, $systemcontext->id);
|
||||||
|
|
||||||
|
// Should not get any, because all of them are prohibited at system level.
|
||||||
|
// Even if we try to allow an specific category.
|
||||||
|
list($categories, $courses) = get_user_capability_contexts($cap, true, $u4->id, true, '', '', '', 'id');
|
||||||
|
$this->assertFalse($categories);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts an array of course ids to make the above test script shorter.
|
* Extracts an array of course ids to make the above test script shorter.
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
$version = 2021101300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
$version = 2021101300.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||||
// RR = release increments - 00 in DEV branches.
|
// RR = release increments - 00 in DEV branches.
|
||||||
// .XX = incremental changes.
|
// .XX = incremental changes.
|
||||||
$release = '4.0dev+ (Build: 20211013)'; // Human-friendly version name
|
$release = '4.0dev+ (Build: 20211013)'; // Human-friendly version name
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue