Merge branch 'MDL-67585-master-4' of git://github.com/snake/moodle
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=12;break}e=c.target.closest(d.default.elements.sectionmodchooser);a.t0=p;a.next=5;return g();case 5:a.t1=a.sent;a.t2=e.dataset.sectionid;f=(0,a.t0)(a.t1,a.t2);a.next=10;return q(f);case 10:h=a.sent;b.displayChooser(e,h,f);case 12:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.allmodules.forEach(function(a){a.urls.addoption+="§ion="+b});return c.allmodules},q=function(a){return s(r(a))},r=function(a){var b=[],c=[],d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})}});
|
||||
define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=12;break}e=c.target.closest(d.default.elements.sectionmodchooser);a.t0=p;a.next=5;return g();case 5:a.t1=a.sent;a.t2=e.dataset.sectionid;f=(0,a.t0)(a.t1,a.t2);a.next=10;return q(f);case 10:h=a.sent;b.displayChooser(e,h,f);case 12:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="§ion="+b});return c.content_items},q=function(a){return s(r(a))},r=function(a){var b=[],c=[],d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})}});
|
||||
//# sourceMappingURL=activitychooser.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_activity_picker_info",args:{courseid:a}}])[0]};a.activityModules=c});
|
||||
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c});
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args"],"mappings":"oLAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_activity_picker_info',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
|
||||
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args"],"mappings":"oLAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
|
||||
@@ -100,10 +100,10 @@ const registerListenerEvents = (courseId) => {
|
||||
const sectionIdMapper = (webServiceData, id) => {
|
||||
// We need to take a fresh deep copy of the original data as an object is a reference type.
|
||||
const newData = JSON.parse(JSON.stringify(webServiceData));
|
||||
newData.allmodules.forEach((module) => {
|
||||
module.urls.addoption += '§ion=' + id;
|
||||
newData.content_items.forEach((module) => {
|
||||
module.link += '§ion=' + id;
|
||||
});
|
||||
return newData.allmodules;
|
||||
return newData.content_items;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -318,7 +318,7 @@ export const displayChooser = (origin, modal, sectionModules) => {
|
||||
// Make a map so we can quickly fetch a specific module's object for either rendering or searching.
|
||||
const mappedModules = new Map();
|
||||
sectionModules.forEach((module) => {
|
||||
mappedModules.set(module.modulename, module);
|
||||
mappedModules.set(module.componentname + '_' + module.link, module);
|
||||
});
|
||||
|
||||
// Register event listeners.
|
||||
|
||||
@@ -31,7 +31,7 @@ import ajax from 'core/ajax';
|
||||
*/
|
||||
export const activityModules = (courseid) => {
|
||||
const request = {
|
||||
methodname: 'core_course_get_activity_picker_info',
|
||||
methodname: 'core_course_get_course_content_items',
|
||||
args: {
|
||||
courseid: courseid,
|
||||
},
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Author exporter.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2019 Mihail Geshoski <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_course\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Course module chooser exporter.
|
||||
*
|
||||
* @copyright 2019 Mihail Geshoski <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_module_chooser_exporter extends exporter {
|
||||
|
||||
/** @var array $modules Array containing the available modules */
|
||||
private $modules;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $modules The available course modules
|
||||
* @param array $related The related data for the export
|
||||
*/
|
||||
public function __construct(array $modules, array $related = []) {
|
||||
$this->modules = $modules;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'options' => [
|
||||
'multiple' => true,
|
||||
'optional' => true,
|
||||
'type' => [
|
||||
'label' => ['type' => PARAM_TEXT],
|
||||
'modulename' => ['type' => PARAM_TEXT],
|
||||
'description' => ['type' => PARAM_TEXT],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'addoption' => [
|
||||
'type' => PARAM_URL
|
||||
]
|
||||
]
|
||||
],
|
||||
'icon' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
|
||||
$options = new \stdClass();
|
||||
$options->trusted = false;
|
||||
$options->noclean = false;
|
||||
$options->smiley = false;
|
||||
$options->filter = false;
|
||||
$options->para = true;
|
||||
$options->newlines = false;
|
||||
$options->overflowdiv = false;
|
||||
|
||||
$context = $this->related['context'];
|
||||
|
||||
$modulesdata = [];
|
||||
foreach ($this->modules as $module) {
|
||||
$customiconurl = null;
|
||||
|
||||
// The property 'name' may contain more than just the module, in which case we need to extract the true module name.
|
||||
$modulename = $module->name;
|
||||
if ($colon = strpos($modulename, ':')) {
|
||||
$modulename = substr($modulename, 0, $colon);
|
||||
}
|
||||
|
||||
if (isset($module->help) || !empty($module->help)) {
|
||||
list($description) = external_format_text((string) $module->help, FORMAT_MARKDOWN,
|
||||
$context->id, null, null, null, $options);
|
||||
} else {
|
||||
$description = get_string('nohelpforactivityorresource', 'moodle');
|
||||
}
|
||||
|
||||
$icon = new \pix_icon('icon', '', $modulename);
|
||||
|
||||
// When exporting check if the title is an object, we assume it's a lang string object otherwise we send the raw string.
|
||||
$modulesdata[] = [
|
||||
'label' => $module->title instanceof \lang_string ? $module->title->out() : $module->title,
|
||||
'modulename' => $modulename,
|
||||
'description' => $description,
|
||||
'urls' => [
|
||||
'addoption' => $module->link->out(false),
|
||||
],
|
||||
'icon' => $icon->export_for_template($output)
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'options' => $modulesdata
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'context' => 'context'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the content_item class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\entity;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* The content_item class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item {
|
||||
/** @var int $id the id. */
|
||||
private $id;
|
||||
|
||||
/** @var string $name the name. */
|
||||
private $name;
|
||||
|
||||
/** @var title $title the title. */
|
||||
private $title;
|
||||
|
||||
/** @var \moodle_url $link the url for the content item's setup page (usually mod/edit.php). */
|
||||
private $link;
|
||||
|
||||
/** @var string $icon an html string containing the icon for this item. */
|
||||
private $icon;
|
||||
|
||||
/** @var string $help the description/help text for this content item. */
|
||||
private $help;
|
||||
|
||||
/** @var int $achetype a module archetype, e.g. MOD_ARCHETYPE_RESOURCE, MOD_ARCHETYPE_OTHER. */
|
||||
private $archetype;
|
||||
|
||||
/** @var string $componentname the name of the component from which this content item originates. */
|
||||
private $componentname;
|
||||
|
||||
/**
|
||||
* The content_item constructor.
|
||||
*
|
||||
* @param int $id Id number.
|
||||
* @param string $name Name of the item, not human readable.
|
||||
* @param title $title Human readable title for the item.
|
||||
* @param \moodle_url $link The URL to the creation page, with any item specific params
|
||||
* @param string $icon HTML containing the icon for the item
|
||||
* @param string $help The description of the item.
|
||||
* @param int $archetype the archetype for the content item (see MOD_ARCHETYPE_X definitions in lib/moodlelib.php).
|
||||
* @param string $componentname the name of the component/plugin with which this content item is associated.
|
||||
*/
|
||||
public function __construct(int $id, string $name, title $title, \moodle_url $link, string $icon, string $help,
|
||||
int $archetype, string $componentname) {
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->title = $title;
|
||||
$this->link = $link;
|
||||
$this->icon = $icon;
|
||||
$this->help = $help;
|
||||
$this->archetype = $archetype;
|
||||
$this->componentname = $componentname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the component with which this content item is associated.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_component_name(): string {
|
||||
return $this->componentname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help description of this item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_help(): string {
|
||||
return $this->help;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the archetype of this item.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_archetype(): int {
|
||||
return $this->archetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of this item.
|
||||
* @return int
|
||||
*/
|
||||
public function get_id(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human readable title of this item.
|
||||
*
|
||||
* @return title
|
||||
*/
|
||||
public function get_title(): title {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to the creation page of this item.
|
||||
*
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public function get_link(): \moodle_url {
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon html for this item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_icon(): string {
|
||||
return $this->icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the lang_string_title class of value object, providing access to the value of a lang string.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\entity;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* The lang_string_title class of value object, providing access to the value of a lang string.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class lang_string_title implements title {
|
||||
|
||||
/** @var string $component the component name. */
|
||||
private $component;
|
||||
|
||||
/** @var string $identifier the string identifier. */
|
||||
private $identifier;
|
||||
|
||||
/**
|
||||
* The lang_string_title constructor.
|
||||
*
|
||||
* @param string $identifier the component name.
|
||||
* @param string $component the string identifier.
|
||||
*/
|
||||
public function __construct(string $identifier, string $component) {
|
||||
$this->identifier = $identifier;
|
||||
$this->component = $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the wrapped string.
|
||||
*
|
||||
* @return string the value of the string.
|
||||
*/
|
||||
public function get_value(): string {
|
||||
return get_string($this->identifier, $this->component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the string_title class of value object, which provides access to a simple string.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\entity;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* The string_title class of value object, which provides access to a simple string.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class string_title implements title {
|
||||
|
||||
/** @var string $title the title string. */
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* The string_title constructor.
|
||||
*
|
||||
* @param string $title a string.
|
||||
*/
|
||||
public function __construct(string $title) {
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the wrapped string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_value(): string {
|
||||
return $this->title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the title value object interface, which provides a basic interface to a string.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\entity;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
interface title {
|
||||
|
||||
/**
|
||||
* Get the value of this title.
|
||||
*/
|
||||
public function get_value(): string;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the course_content_item_exporter class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\exporter;
|
||||
use core_course\local\entity\content_item;
|
||||
|
||||
/**
|
||||
* The course_content_item_exporter class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_content_item_exporter extends exporter {
|
||||
|
||||
/** @var content_item $contentitem the content_item to export. */
|
||||
private $contentitem;
|
||||
|
||||
/**
|
||||
* The course_content_item_exporter constructor.
|
||||
*
|
||||
* @param content_item $contentitem the content item to export.
|
||||
* @param array $related the array of related objects used during export.
|
||||
*/
|
||||
public function __construct(content_item $contentitem, array $related = []) {
|
||||
$this->contentitem = $contentitem;
|
||||
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of all properties originating in the export target, \core_course\local\entity\content_item.
|
||||
*
|
||||
* @return array The array of property values, indexed by name.
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'id' => ['type' => PARAM_INT, 'description' => 'The id of the content item'],
|
||||
'name' => ['type' => PARAM_TEXT, 'description' => 'Name of the content item'],
|
||||
'title' => ['type' => PARAM_TEXT, 'description' => 'The string title of the content item, human readable'],
|
||||
'link' => ['type' => PARAM_URL, 'description' => 'The link to the content item creation page'],
|
||||
'icon' => ['type' => PARAM_RAW, 'description' => 'Html containing the icon for the content item'],
|
||||
'help' => ['type' => PARAM_RAW, 'description' => 'Html description / help for the content item'],
|
||||
'archetype' => ['type' => PARAM_RAW, 'description' => 'The archetype of the module exposing the content item'],
|
||||
'componentname' => ['type' => PARAM_TEXT, 'description' => 'The name of the component exposing the content item'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of all properties which are either calculated or originate in a related domain object.
|
||||
*
|
||||
* @return array The array of property values, indexed by name.
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
// This will hold user-dependant properties such as whether the item is starred or recommended.
|
||||
return [
|
||||
'favourite' => ['type' => PARAM_BOOL, 'description' => 'Has the user favourited the content item'],
|
||||
'legacyitem' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'description' => 'If this item was pulled from the old callback and has no item id.'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ALL properties for the content_item DTO being exported.
|
||||
*
|
||||
* These properties are a mix of:
|
||||
* - readonly properties of the primary object (content_item) being exported.
|
||||
* - calculated values
|
||||
* - properties originating from the related domain objects.
|
||||
*
|
||||
* Normally, those properties defined in get_properties() are added to the export automatically as part of the superclass code,
|
||||
* provided they are public properties on the export target. In this case, the export target is content_item, which doesn't
|
||||
* provide public access to its properties, so those are fetched via their respective getters here.
|
||||
*
|
||||
* @param \renderer_base $output
|
||||
* @return array The array of property values, indexed by name.
|
||||
*/
|
||||
protected function get_other_values(\renderer_base $output) {
|
||||
|
||||
$favourite = false;
|
||||
$itemtype = 'contentitem_' . $this->contentitem->get_component_name();
|
||||
if (isset($this->related['favouriteitems'])) {
|
||||
foreach ($this->related['favouriteitems'] as $favobj) {
|
||||
if ($favobj->itemtype === $itemtype && in_array($this->contentitem->get_id(), $favobj->ids)) {
|
||||
$favourite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$properties = [
|
||||
'id' => $this->contentitem->get_id(),
|
||||
'name' => $this->contentitem->get_name(),
|
||||
'title' => $this->contentitem->get_title()->get_value(),
|
||||
'link' => $this->contentitem->get_link()->out(false),
|
||||
'icon' => $this->contentitem->get_icon(),
|
||||
'help' => $this->contentitem->get_help(),
|
||||
'archetype' => $this->contentitem->get_archetype(),
|
||||
'componentname' => $this->contentitem->get_component_name(),
|
||||
'favourite' => $favourite,
|
||||
'legacyitem' => ($this->contentitem->get_id() == -1)
|
||||
];
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the list of related objects, used by this exporter.
|
||||
*
|
||||
* @return array the list of related objects.
|
||||
*/
|
||||
protected static function define_related(): array {
|
||||
return [
|
||||
'context' => '\context',
|
||||
'favouriteitems' => '\stdClass[]?'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the course_content_items_exporter class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\exporter;
|
||||
use core_course\local\entity\content_item;
|
||||
|
||||
/**
|
||||
* The course_content_items_exporter class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_content_items_exporter extends exporter {
|
||||
|
||||
/** @var content_item[] the array of content items. */
|
||||
private $contentitems;
|
||||
|
||||
/**
|
||||
* The course_content_items_exporter constructor.
|
||||
*
|
||||
* @param array $contentitems the array of \core_course\local\entity\content_item objects to export.
|
||||
* @param array $related any related objects, see define_related for what's expected.
|
||||
*/
|
||||
public function __construct(array $contentitems, array $related) {
|
||||
$this->contentitems = $contentitems;
|
||||
|
||||
parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties defining this export.
|
||||
*
|
||||
* @return array the array of properties.
|
||||
*/
|
||||
public static function define_properties() {
|
||||
return [
|
||||
'content_items' => [
|
||||
'type' => course_content_item_exporter::read_properties_definition(),
|
||||
'multiple' => true
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return the data for this export.
|
||||
*
|
||||
* @param \renderer_base $output
|
||||
* @return array the array of course content_items
|
||||
*/
|
||||
protected function get_other_values(\renderer_base $output) {
|
||||
|
||||
$contentitemexport = function(content_item $contentitem) use ($output) {
|
||||
$exporter = new course_content_item_exporter(
|
||||
$contentitem,
|
||||
[
|
||||
'context' => $this->related['context'],
|
||||
'favouriteitems' => $this->related['favouriteitems'],
|
||||
]
|
||||
);
|
||||
return $exporter->export($output);
|
||||
};
|
||||
|
||||
$exportedcontentitems = array_map($contentitemexport, $this->contentitems);
|
||||
|
||||
return [
|
||||
'content_items' => $exportedcontentitems
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the list of related objects, used by this exporter.
|
||||
*
|
||||
* @return array the list of related objects.
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'context' => '\context',
|
||||
'favouriteitems' => '\stdClass[]?'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Contains the service_factory, a locator for services for course content items.
|
||||
*
|
||||
* Services encapsulate the business logic, and any data manipulation code, and are what clients should interact with.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2020 Adrian Greeve <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\factory;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\repository\caching_content_item_readonly_repository;
|
||||
use core_course\local\repository\content_item_readonly_repository;
|
||||
use core_course\local\service\content_item_service;
|
||||
|
||||
/**
|
||||
* Class service_factory, providing functions for location of service objects for course content items.
|
||||
*
|
||||
* This class is responsible for providing service objects to clients only.
|
||||
*
|
||||
* @copyright 2020 Adrian Greeve <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item_service_factory {
|
||||
|
||||
/**
|
||||
* Returns a basic service object providing operations for course content items.
|
||||
*
|
||||
* @return content_item_service
|
||||
*/
|
||||
public static function get_content_item_service(): content_item_service {
|
||||
return new content_item_service(
|
||||
new caching_content_item_readonly_repository(
|
||||
\cache::make('core', 'user_course_content_items'),
|
||||
new content_item_readonly_repository()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains class caching_content_item_repository, for fetching content_items, with additional caching.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\repository;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* The class caching_content_item_repository, for fetching content_items, with additional caching.
|
||||
*
|
||||
* This class decorates the content_item_repository and uses the supplied cache to store content items for user and course
|
||||
* combinations. The content items for subsequent calls are returned from the cache if present, else are retrieved from the wrapped
|
||||
* content_item_repository.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class caching_content_item_readonly_repository implements content_item_readonly_repository_interface {
|
||||
|
||||
/** @var \cache $cachestore the cache to use. */
|
||||
private $cachestore;
|
||||
|
||||
/** @var content_item_readonly_repository $contentitemrepository a content item repository. */
|
||||
private $contentitemrepository;
|
||||
|
||||
/**
|
||||
* The caching_content_item_readonly_repository constructor.
|
||||
*
|
||||
* @param \cache $cachestore a cache to use.
|
||||
* @param content_item_readonly_repository $contentitemrepository the repository to use as a fallback, after a cache miss.
|
||||
*/
|
||||
public function __construct(\cache $cachestore, content_item_readonly_repository $contentitemrepository) {
|
||||
$this->cachestore = $cachestore;
|
||||
$this->contentitemrepository = $contentitemrepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the content items for a given course and user.
|
||||
*
|
||||
* @param \stdClass $course The course to find content items for.
|
||||
* @param \stdClass $user the user to pass to plugins.
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
public function find_all_for_course(\stdClass $course, \stdClass $user): array {
|
||||
global $USER;
|
||||
// Try to find this data in the cache first.
|
||||
$key = $USER->id . '_' . $course->id;
|
||||
$contentitems = $this->cachestore->get($key);
|
||||
if ($contentitems !== false) {
|
||||
return $contentitems;
|
||||
}
|
||||
|
||||
// If we can't find it there, we must get it from the slow data store, updating the cache in the process.
|
||||
$contentitems = $this->contentitemrepository->find_all_for_course($course, $user);
|
||||
$this->cachestore->set($key, $contentitems);
|
||||
return $contentitems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the content items made available by core and plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function find_all(): array {
|
||||
return $this->contentitemrepository->find_all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains class content_item_repository, for fetching content_items.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\repository;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\entity\content_item;
|
||||
use core_course\local\entity\lang_string_title;
|
||||
use core_course\local\entity\string_title;
|
||||
|
||||
/**
|
||||
* The class content_item_repository, for reading content_items.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item_readonly_repository implements content_item_readonly_repository_interface {
|
||||
/**
|
||||
* Get the help string for content items representing core modules.
|
||||
*
|
||||
* @param string $modname the module name.
|
||||
* @return string the help string, including help link.
|
||||
*/
|
||||
private function get_core_module_help_string(string $modname): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$help = '';
|
||||
$sm = get_string_manager();
|
||||
if ($sm->string_exists('modulename_help', $modname)) {
|
||||
$help = get_string('modulename_help', $modname);
|
||||
if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
|
||||
$link = get_string('modulename_link', $modname);
|
||||
$linktext = get_string('morehelp');
|
||||
$help .= \html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), ['class' => 'helpdoclink']);
|
||||
}
|
||||
}
|
||||
return $help;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a content_item object based on legacy data returned from the get_shortcuts hook implementations.
|
||||
*
|
||||
* @param \stdClass $item the stdClass of legacy data.
|
||||
* @return content_item a content item object.
|
||||
*/
|
||||
private function content_item_from_legacy_data(\stdClass $item): content_item {
|
||||
global $OUTPUT;
|
||||
|
||||
// Make sure the legacy data results in a content_item with id = 0.
|
||||
// Even with an id, we can't uniquely identify the item, because we can't guarantee what component it came from.
|
||||
// An id of -1, signifies this.
|
||||
$item->id = -1;
|
||||
|
||||
// If the module provides the helplink property, append it to the help text to match the look and feel
|
||||
// of the default course modules.
|
||||
if (isset($item->help) && isset($item->helplink)) {
|
||||
$linktext = get_string('morehelp');
|
||||
$item->help .= \html_writer::tag('div',
|
||||
$OUTPUT->doc_link($item->helplink, $linktext, true), ['class' => 'helpdoclink']);
|
||||
}
|
||||
|
||||
if (is_string($item->title)) {
|
||||
$item->title = new string_title($item->title);
|
||||
} else if ($item->title instanceof \lang_string) {
|
||||
$item->title = new lang_string_title($item->title->get_identifier(), $item->title->get_component());
|
||||
}
|
||||
|
||||
// Legacy items had names which are in one of 2 forms:
|
||||
// modname, i.e. 'assign' or
|
||||
// modname:link, i.e. lti:http://etc...
|
||||
// We need to grab the module name out to create the componentname.
|
||||
$modname = (strpos($item->name, ':') !== false) ? explode(':', $item->name)[0] : $item->name;
|
||||
|
||||
return new content_item($item->id, $item->name, $item->title, $item->link, $item->icon, $item->help ?? '',
|
||||
$item->archetype, 'mod_' . $modname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stdClass type object based on a content_item instance.
|
||||
*
|
||||
* @param content_item $contentitem
|
||||
* @return \stdClass the legacy data.
|
||||
*/
|
||||
private function content_item_to_legacy_data(content_item $contentitem): \stdClass {
|
||||
$item = new \stdClass();
|
||||
$item->id = $contentitem->get_id();
|
||||
$item->name = $contentitem->get_name();
|
||||
$item->title = $contentitem->get_title();
|
||||
$item->link = $contentitem->get_link();
|
||||
$item->icon = $contentitem->get_icon();
|
||||
$item->help = $contentitem->get_help();
|
||||
$item->archetype = $contentitem->get_archetype();
|
||||
$item->componentname = $contentitem->get_component_name();
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the contentitems from all subplugin hooks for a given module plugin.
|
||||
*
|
||||
* @param string $parentpluginname the name of the module plugin to check subplugins for.
|
||||
* @param content_item $modulecontentitem the content item of the module plugin, to pass to the hooks.
|
||||
* @param \stdClass $user the user object to pass to subplugins.
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
private function get_subplugin_course_content_items(string $parentpluginname, content_item $modulecontentitem,
|
||||
\stdClass $user): array {
|
||||
|
||||
$contentitems = [];
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
foreach ($pluginmanager->get_subplugins_of_plugin($parentpluginname) as $subpluginname => $subplugin) {
|
||||
// Call the hook, but with a copy of the module content item data.
|
||||
$spcontentitems = component_callback($subpluginname, 'get_course_content_items', [$modulecontentitem, $user], null);
|
||||
if (!is_null($spcontentitems)) {
|
||||
foreach ($spcontentitems as $spcontentitem) {
|
||||
$contentitems[] = $spcontentitem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $contentitems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the content items for a subplugin.
|
||||
*
|
||||
* @param string $parentpluginname
|
||||
* @param content_item $modulecontentitem
|
||||
* @return array
|
||||
*/
|
||||
private function get_subplugin_all_content_items(string $parentpluginname, content_item $modulecontentitem): array {
|
||||
$contentitems = [];
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
foreach ($pluginmanager->get_subplugins_of_plugin($parentpluginname) as $subpluginname => $subplugin) {
|
||||
// Call the hook, but with a copy of the module content item data.
|
||||
$spcontentitems = component_callback($subpluginname, 'get_all_content_items', [$modulecontentitem], null);
|
||||
if (!is_null($spcontentitems)) {
|
||||
foreach ($spcontentitems as $spcontentitem) {
|
||||
$contentitems[] = $spcontentitem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $contentitems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to make sure any legacy items have certain properties, which, if missing are inherited from the parent module item.
|
||||
*
|
||||
* @param \stdClass $legacyitem the legacy information, a stdClass coming from get_shortcuts() hook.
|
||||
* @param content_item $modulecontentitem The module's content item information, to inherit if needed.
|
||||
* @return \stdClass the updated legacy item stdClass
|
||||
*/
|
||||
private function legacy_item_inherit_missing(\stdClass $legacyitem, content_item $modulecontentitem): \stdClass {
|
||||
// Fall back to the plugin parent value if the subtype didn't provide anything.
|
||||
$legacyitem->archetype = $legacyitem->archetype ?? $modulecontentitem->get_archetype();
|
||||
$legacyitem->icon = $legacyitem->icon ?? $modulecontentitem->get_icon();
|
||||
return $legacyitem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the available content items, not restricted to course or user.
|
||||
*
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
public function find_all(): array {
|
||||
global $OUTPUT, $DB;
|
||||
|
||||
// Get all modules so we know which plugins are enabled and able to add content.
|
||||
// Only module plugins may add content items.
|
||||
$modules = $DB->get_records('modules', ['visible' => 1]);
|
||||
$return = [];
|
||||
|
||||
// Now, generate the content_items.
|
||||
foreach ($modules as $modid => $mod) {
|
||||
// Create the content item for the module itself.
|
||||
// If the module chooses to implement the hook, this may be thrown away.
|
||||
$help = $this->get_core_module_help_string($mod->name);
|
||||
$archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
|
||||
|
||||
$contentitem = new content_item(
|
||||
$mod->id,
|
||||
$mod->name,
|
||||
new lang_string_title("modulename", $mod->name),
|
||||
new \moodle_url(''), // No course scope, so just an empty link.
|
||||
$OUTPUT->pix_icon('icon', '', $mod->name, ['class' => 'icon']),
|
||||
$help,
|
||||
$archetype,
|
||||
'mod_' . $mod->name
|
||||
);
|
||||
|
||||
$modcontentitemreference = clone($contentitem);
|
||||
|
||||
if (component_callback_exists('mod_' . $mod->name, 'get_all_content_items')) {
|
||||
// Call the module hooks for this module.
|
||||
$plugincontentitems = component_callback('mod_' . $mod->name, 'get_all_content_items',
|
||||
[$modcontentitemreference], []);
|
||||
if (!empty($plugincontentitems)) {
|
||||
array_push($return, ...$plugincontentitems);
|
||||
}
|
||||
|
||||
// Now, get those for subplugins of the module.
|
||||
$subplugincontentitems = $this->get_subplugin_all_content_items('mod_' . $mod->name, $modcontentitemreference);
|
||||
if (!empty($subplugincontentitems)) {
|
||||
array_push($return, ...$subplugincontentitems);
|
||||
}
|
||||
} else {
|
||||
// Neither callback was found, so just use the default module content item.
|
||||
$return[] = $contentitem;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of potential content items for the given course.
|
||||
*
|
||||
* @param \stdClass $course the course
|
||||
* @param \stdClass $user the user, to pass to plugins implementing callbacks.
|
||||
* @return array the array of content_item objects
|
||||
*/
|
||||
public function find_all_for_course(\stdClass $course, \stdClass $user): array {
|
||||
global $OUTPUT, $DB;
|
||||
|
||||
// Get all modules so we know which plugins are enabled and able to add content.
|
||||
// Only module plugins may add content items.
|
||||
$modules = $DB->get_records('modules', ['visible' => 1]);
|
||||
$return = [];
|
||||
|
||||
// A moodle_url is expected and required by modules in their implementation of the hook 'get_shortcuts'.
|
||||
$urlbase = new \moodle_url('/course/mod.php', ['id' => $course->id]);
|
||||
|
||||
// Now, generate the content_items.
|
||||
foreach ($modules as $modid => $mod) {
|
||||
|
||||
// Create the content item for the module itself.
|
||||
// If the module chooses to implement the hook, this may be thrown away.
|
||||
$help = $this->get_core_module_help_string($mod->name);
|
||||
$archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
|
||||
|
||||
$contentitem = new content_item(
|
||||
$mod->id,
|
||||
$mod->name,
|
||||
new lang_string_title("modulename", $mod->name),
|
||||
new \moodle_url($urlbase, ['add' => $mod->name]),
|
||||
$OUTPUT->pix_icon('icon', '', $mod->name, ['class' => 'icon']),
|
||||
$help,
|
||||
$archetype,
|
||||
'mod_' . $mod->name
|
||||
);
|
||||
|
||||
// Legacy vs new hooks.
|
||||
// If the new hook is found for a module plugin, use that path (calling mod plugins and their subplugins directly)
|
||||
// If not, check the legacy hook. This won't provide us with enough information to identify items uniquely within their
|
||||
// component (lti + lti source being an example), but we can still list these items.
|
||||
$modcontentitemreference = clone($contentitem);
|
||||
|
||||
if (component_callback_exists('mod_' . $mod->name, 'get_course_content_items')) {
|
||||
// Call the module hooks for this module.
|
||||
$plugincontentitems = component_callback('mod_' . $mod->name, 'get_course_content_items',
|
||||
[$modcontentitemreference, $user, $course], []);
|
||||
if (!empty($plugincontentitems)) {
|
||||
array_push($return, ...$plugincontentitems);
|
||||
}
|
||||
|
||||
// Now, get those for subplugins of the module.
|
||||
$subpluginitems = $this->get_subplugin_course_content_items('mod_' . $mod->name, $modcontentitemreference, $user);
|
||||
if (!empty($subpluginitems)) {
|
||||
array_push($return, ...$subpluginitems);
|
||||
}
|
||||
|
||||
} else if (component_callback_exists('mod_' . $mod->name, 'get_shortcuts')) {
|
||||
// TODO: MDL-68011 this block needs to be removed in 4.3.
|
||||
debugging('The callback get_shortcuts has been deprecated. Please use get_course_content_items and
|
||||
get_all_content_items instead. Some features of the activity chooser, such as favourites and recommendations
|
||||
are not supported when providing content items via the deprecated callback.');
|
||||
|
||||
// If get_shortcuts() callback is defined, the default module action is not added.
|
||||
// It is a responsibility of the callback to add it to the return value unless it is not needed.
|
||||
// The legacy hook, get_shortcuts, expects a stdClass representation of the core module content_item entry.
|
||||
$modcontentitemreference = $this->content_item_to_legacy_data($contentitem);
|
||||
|
||||
$legacyitems = component_callback($mod->name, 'get_shortcuts', [$modcontentitemreference], null);
|
||||
if (!is_null($legacyitems)) {
|
||||
foreach ($legacyitems as $legacyitem) {
|
||||
|
||||
$legacyitem = $this->legacy_item_inherit_missing($legacyitem, $contentitem);
|
||||
|
||||
// All items must have different links, use them as a key in the return array.
|
||||
// If plugin returned the only one item with the same link as default item - keep $modname,
|
||||
// otherwise append the link url to the module name.
|
||||
$legacyitem->name = (count($legacyitems) == 1 &&
|
||||
$legacyitem->link->out() === $contentitem->get_link()->out()) ? $mod->name : $mod->name . ':' .
|
||||
$legacyitem->link;
|
||||
|
||||
$plugincontentitem = $this->content_item_from_legacy_data($legacyitem);
|
||||
|
||||
$return[] = $plugincontentitem;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Neither callback was found, so just use the default module content item.
|
||||
$return[] = $contentitem;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the interface content_item_readonly_repository_interface, defining operations for readonly content item repositories.
|
||||
*
|
||||
* This interface is not considered a published interface and serves to govern internal, local repository objects only.
|
||||
* All calling code should use instances of the service classes, and should not interact with repositories directly.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\repository;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
interface content_item_readonly_repository_interface {
|
||||
/**
|
||||
* Find all content items for a given course and user.
|
||||
*
|
||||
* @param \stdClass $course the course object.
|
||||
* @param \stdClass $user the user object.
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
public function find_all_for_course(\stdClass $course, \stdClass $user): array;
|
||||
|
||||
/**
|
||||
* Find all content items that can be presented, irrespective of course.
|
||||
*
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
public function find_all(): array;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the content_item_service class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_course\local\service;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\exporters\course_content_items_exporter;
|
||||
use core_course\local\repository\content_item_readonly_repository_interface;
|
||||
|
||||
/**
|
||||
* The content_item_service class, providing the api for interacting with content items.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item_service {
|
||||
|
||||
/** @var content_item_readonly_repository_interface $repository a repository for content items. */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* The content_item_service constructor.
|
||||
*
|
||||
* @param content_item_readonly_repository_interface $repository a content item repository.
|
||||
*/
|
||||
public function __construct(content_item_readonly_repository_interface $repository) {
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects representing favourited content items.
|
||||
*
|
||||
* Each object contains the following properties:
|
||||
* itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
|
||||
* ids[]: an array of ids, representing the content items within a component.
|
||||
*
|
||||
* Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
* @return array
|
||||
*/
|
||||
private function get_favourite_content_items_for_user(\stdClass $user): array {
|
||||
$favcache = \cache::make('core', 'user_favourite_course_content_items');
|
||||
$key = $user->id;
|
||||
$favmods = $favcache->get($key);
|
||||
if ($favmods !== false) {
|
||||
return $favmods;
|
||||
}
|
||||
|
||||
// Get all modules and any submodules which implement get_course_content_items() hook.
|
||||
// This gives us the set of all itemtypes which we'll use to register favourite content items.
|
||||
// The ids that each plugin returns will be used together with the itemtype to uniquely identify
|
||||
// each content item for favouriting.
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
$plugins = $pluginmanager->get_plugins_of_type('mod');
|
||||
$itemtypes = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
// Add the mod itself.
|
||||
$itemtypes[] = 'contentitem_mod_' . $plugin->name;
|
||||
|
||||
// Add any subplugins to the list of item types.
|
||||
$subplugins = $pluginmanager->get_subplugins_of_plugin('mod_' . $plugin->name);
|
||||
foreach ($subplugins as $subpluginname => $subplugininfo) {
|
||||
if (component_callback_exists($subpluginname, 'get_course_content_items')) {
|
||||
$itemtypes[] = 'contentitem_' . $subpluginname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($user->id));
|
||||
$favourites = [];
|
||||
foreach ($itemtypes as $itemtype) {
|
||||
$favs = $ufservice->find_favourites_by_type('core_course', $itemtype);
|
||||
$favobj = (object) ['itemtype' => $itemtype, 'ids' => array_column($favs, 'itemid')];
|
||||
$favourites[] = $favobj;
|
||||
}
|
||||
$favcache->set($key, $favourites);
|
||||
return $favourites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc.
|
||||
*
|
||||
* @param \stdClass $user the user object.
|
||||
* @return array the array of exported content items.
|
||||
*/
|
||||
public function get_all_content_items(\stdClass $user): array {
|
||||
global $PAGE;
|
||||
$allcontentitems = $this->repository->find_all();
|
||||
|
||||
// Export the objects to get the formatted objects for transfer/display.
|
||||
$favourites = $this->get_favourite_content_items_for_user($user);
|
||||
$ciexporter = new course_content_items_exporter(
|
||||
$allcontentitems,
|
||||
[
|
||||
'context' => \context_system::instance(),
|
||||
'favouriteitems' => $favourites
|
||||
]
|
||||
);
|
||||
$exported = $ciexporter->export($PAGE->get_renderer('core'));
|
||||
|
||||
// Sort by title for return.
|
||||
usort($exported->content_items, function($a, $b) {
|
||||
return $a->title > $b->title;
|
||||
});
|
||||
|
||||
return $exported->content_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a representation of the available content items, for a user in a course.
|
||||
*
|
||||
* @param \stdClass $user the user to check access for.
|
||||
* @param \stdClass $course the course to scope the content items to.
|
||||
* @param array $linkparams the desired section to return to.
|
||||
* @return \stdClass[] the content items, scoped to a course.
|
||||
*/
|
||||
public function get_content_items_for_user_in_course(\stdClass $user, \stdClass $course, array $linkparams = []): array {
|
||||
global $PAGE;
|
||||
|
||||
if (!has_capability('moodle/course:manageactivities', \context_course::instance($course->id), $user)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all the visible content items.
|
||||
$allcontentitems = $this->repository->find_all_for_course($course, $user);
|
||||
|
||||
// Content items can only originate from modules or submodules.
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
$components = \core_component::get_component_list();
|
||||
$parents = [];
|
||||
foreach ($allcontentitems as $contentitem) {
|
||||
if (!in_array($contentitem->get_component_name(), array_keys($components['mod']))) {
|
||||
// It could be a subplugin.
|
||||
$info = $pluginmanager->get_plugin_info($contentitem->get_component_name());
|
||||
if (!is_null($info)) {
|
||||
$parent = $info->get_parent_plugin();
|
||||
if ($parent != false) {
|
||||
if (in_array($parent, array_keys($components['mod']))) {
|
||||
$parents[$contentitem->get_component_name()] = $parent;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new \moodle_exception('Only modules and submodules can generate content items. \''
|
||||
. $contentitem->get_component_name() . '\' is neither.');
|
||||
}
|
||||
$parents[$contentitem->get_component_name()] = $contentitem->get_component_name();
|
||||
}
|
||||
|
||||
// Now, check access to these items for the user.
|
||||
$availablecontentitems = array_filter($allcontentitems, function($contentitem) use ($course, $user, $parents) {
|
||||
// Check the parent module access for the user.
|
||||
return course_allowed_module($course, explode('_', $parents[$contentitem->get_component_name()])[1], $user);
|
||||
});
|
||||
|
||||
// Add the link params to the link, if any have been provided.
|
||||
if (!empty($linkparams)) {
|
||||
$availablecontentitems = array_map(function ($item) use ($linkparams) {
|
||||
$item->get_link()->params($linkparams);
|
||||
return $item;
|
||||
}, $availablecontentitems);
|
||||
}
|
||||
|
||||
// Export the objects to get the formatted objects for transfer/display.
|
||||
$favourites = $this->get_favourite_content_items_for_user($user);
|
||||
$ciexporter = new course_content_items_exporter(
|
||||
$availablecontentitems,
|
||||
[
|
||||
'context' => \context_course::instance($course->id),
|
||||
'favouriteitems' => $favourites
|
||||
]
|
||||
);
|
||||
$exported = $ciexporter->export($PAGE->get_renderer('course'));
|
||||
|
||||
// Sort by title for return.
|
||||
usort($exported->content_items, function($a, $b) {
|
||||
return $a->title > $b->title;
|
||||
});
|
||||
|
||||
return $exported->content_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a content item to a user's favourites.
|
||||
*
|
||||
* @param \stdClass $user the user whose favourite this is.
|
||||
* @param string $componentname the name of the component from which the content item originates.
|
||||
* @param int $contentitemid the id of the content item.
|
||||
* @return \stdClass the exported content item.
|
||||
*/
|
||||
public function add_to_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
|
||||
$usercontext = \context_user::instance($user->id);
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
|
||||
|
||||
// Because each plugin decides its own ids for content items, a combination of
|
||||
// itemtype and id is used to guarantee uniqueness across all content items.
|
||||
$itemtype = 'contentitem_' . $componentname;
|
||||
|
||||
$ufservice->create_favourite('core_course', $itemtype, $contentitemid, $usercontext);
|
||||
|
||||
$favcache = \cache::make('core', 'user_favourite_course_content_items');
|
||||
$favcache->delete($user->id);
|
||||
|
||||
$items = $this->get_all_content_items($user);
|
||||
return $items[array_search($contentitemid, array_column($items, 'id'))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the content item from a user's favourites.
|
||||
*
|
||||
* @param \stdClass $user the user whose favourite this is.
|
||||
* @param string $componentname the name of the component from which the content item originates.
|
||||
* @param int $contentitemid the id of the content item.
|
||||
* @return \stdClass the exported content item.
|
||||
*/
|
||||
public function remove_from_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
|
||||
$usercontext = \context_user::instance($user->id);
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
|
||||
|
||||
// Because each plugin decides its own ids for content items, a combination of
|
||||
// itemtype and id is used to guarantee uniqueness across all content items.
|
||||
$itemtype = 'contentitem_' . $componentname;
|
||||
|
||||
$ufservice->delete_favourite('core_course', $itemtype, $contentitemid, $usercontext);
|
||||
|
||||
$favcache = \cache::make('core', 'user_favourite_course_content_items');
|
||||
$favcache->delete($user->id);
|
||||
|
||||
$items = $this->get_all_content_items($user);
|
||||
return $items[array_search($contentitemid, array_column($items, 'id'))];
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ class provider implements
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_subsystem_link('core_completion', [], 'privacy:metadata:completionsummary');
|
||||
$collection->add_subsystem_link('core_favourites', [], 'privacy:metadata:favouritessummary');
|
||||
$collection->add_subsystem_link('core_favourites', [], 'privacy:metadata:activityfavouritessummary');
|
||||
$collection->add_user_preference('coursecat_management_perpage', 'privacy:perpage');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
+110
-45
@@ -4141,33 +4141,114 @@ class core_course_external extends external_api {
|
||||
return new external_single_structure($userfields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function add_content_item_to_user_favourites_parameters() {
|
||||
return new external_function_parameters([
|
||||
'componentname' => new external_value(PARAM_TEXT,
|
||||
'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
|
||||
'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a content item to a user's favourites.
|
||||
*
|
||||
* @param string $componentname the name of the component from which this content item originates.
|
||||
* @param int $contentitemid the id of the content item.
|
||||
* @return stdClass the exporter content item.
|
||||
*/
|
||||
public static function add_content_item_to_user_favourites(string $componentname, int $contentitemid) {
|
||||
global $USER;
|
||||
|
||||
[
|
||||
'componentname' => $componentname,
|
||||
'contentitemid' => $contentitemid,
|
||||
] = self::validate_parameters(self::add_content_item_to_user_favourites_parameters(),
|
||||
[
|
||||
'componentname' => $componentname,
|
||||
'contentitemid' => $contentitemid,
|
||||
]
|
||||
);
|
||||
|
||||
self::validate_context(context_user::instance($USER->id));
|
||||
|
||||
$contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
|
||||
return $contentitemservice->add_to_user_favourites($USER, $componentname, $contentitemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function add_content_item_to_user_favourites_returns() {
|
||||
return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function remove_content_item_from_user_favourites_parameters() {
|
||||
return new external_function_parameters([
|
||||
'componentname' => new external_value(PARAM_TEXT,
|
||||
'frankenstyle name of the component to which the content item belongs', VALUE_REQUIRED),
|
||||
'contentitemid' => new external_value(PARAM_INT, 'id of the content item', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a content item from a user's favourites.
|
||||
*
|
||||
* @param string $componentname the name of the component from which this content item originates.
|
||||
* @param int $contentitemid the id of the content item.
|
||||
* @return stdClass the exported content item.
|
||||
*/
|
||||
public static function remove_content_item_from_user_favourites(string $componentname, int $contentitemid) {
|
||||
global $USER;
|
||||
|
||||
[
|
||||
'componentname' => $componentname,
|
||||
'contentitemid' => $contentitemid,
|
||||
] = self::validate_parameters(self::remove_content_item_from_user_favourites_parameters(),
|
||||
[
|
||||
'componentname' => $componentname,
|
||||
'contentitemid' => $contentitemid,
|
||||
]
|
||||
);
|
||||
|
||||
self::validate_context(context_user::instance($USER->id));
|
||||
|
||||
$contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
|
||||
return $contentitemservice->remove_from_user_favourites($USER, $componentname, $contentitemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value.
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function remove_content_item_from_user_favourites_returns() {
|
||||
return \core_course\local\exporters\course_content_item_exporter::get_read_structure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function fetch_modules_activity_chooser_returns() {
|
||||
public static function get_course_content_items_returns() {
|
||||
return new external_single_structure([
|
||||
'allmodules' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'label' => new external_value(PARAM_TEXT, 'Human readable module name', VALUE_OPTIONAL),
|
||||
'modulename' => new external_value(PARAM_TEXT, 'Module name', VALUE_OPTIONAL),
|
||||
'description' => new external_value(PARAM_RAW, 'Help panel information', VALUE_OPTIONAL),
|
||||
'urls' => new external_single_structure([
|
||||
'addoption' => new external_value(PARAM_URL, 'The edit link for the module', VALUE_OPTIONAL),
|
||||
]),
|
||||
'icon' => new external_single_structure([
|
||||
'attributes' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'name' => new external_value(PARAM_RAW, 'HTML attr', VALUE_OPTIONAL),
|
||||
'value' => new external_value(PARAM_RAW, 'Value of the HTML attr', VALUE_OPTIONAL),
|
||||
])
|
||||
),
|
||||
'extraclasses' => new external_value(PARAM_RAW, 'Anything extra the module defines', VALUE_OPTIONAL),
|
||||
]),
|
||||
])
|
||||
'content_items' => new external_multiple_structure(
|
||||
\core_course\local\exporters\course_content_item_exporter::get_read_structure()
|
||||
),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -4176,7 +4257,7 @@ class core_course_external extends external_api {
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function fetch_modules_activity_chooser_parameters() {
|
||||
public static function get_course_content_items_parameters() {
|
||||
return new external_function_parameters([
|
||||
'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
|
||||
]);
|
||||
@@ -4187,39 +4268,23 @@ class core_course_external extends external_api {
|
||||
*
|
||||
* @param int $courseid The course we want to fetch the modules for
|
||||
* @return array Contains array of modules and their metadata
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function fetch_modules_activity_chooser(int $courseid) {
|
||||
global $DB, $OUTPUT;
|
||||
public static function get_course_content_items(int $courseid) {
|
||||
global $USER;
|
||||
|
||||
[
|
||||
'courseid' => $courseid,
|
||||
] = self::validate_parameters(self::fetch_modules_activity_chooser_parameters(), [
|
||||
] = self::validate_parameters(self::get_course_content_items_parameters(), [
|
||||
'courseid' => $courseid,
|
||||
]);
|
||||
$warnings = array();
|
||||
|
||||
// Validate the course context.
|
||||
$coursecontext = context_course::instance($courseid);
|
||||
self::validate_context($coursecontext);
|
||||
// Check to see if user can add menus and there are modules to add.
|
||||
if (!has_capability('moodle/course:manageactivities', $coursecontext)
|
||||
|| !($modnames = get_module_types_names()) || empty($modnames)) {
|
||||
return '';
|
||||
}
|
||||
$course = get_course($courseid);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
// Retrieve all modules with associated metadata.
|
||||
$modules = get_module_metadata($course, $modnames, null);
|
||||
$related = [
|
||||
'context' => $coursecontext
|
||||
];
|
||||
// Export the module chooser data.
|
||||
$modchooserdata = new \core_course\external\course_module_chooser_exporter($modules, $related);
|
||||
$contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
|
||||
$result = [];
|
||||
$result['allmodules'] = $modchooserdata->export($OUTPUT)->options;
|
||||
$result['warnings'] = $warnings;
|
||||
return $result;
|
||||
$contentitems = $contentitemservice->get_content_items_for_user_in_course($USER, $course);
|
||||
return ['content_items' => $contentitems];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -383,12 +383,30 @@ class format_singleactivity extends format_base {
|
||||
* @return bool|null (null if the check is not possible)
|
||||
*/
|
||||
public function activity_has_subtypes() {
|
||||
global $USER;
|
||||
if (!($modname = $this->get_activitytype())) {
|
||||
return null;
|
||||
}
|
||||
$metadata = get_module_metadata($this->get_course(), self::get_supported_activities());
|
||||
$contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
$metadata = $contentitemservice->get_content_items_for_user_in_course($USER, $this->get_course());
|
||||
|
||||
// If there are multiple items originating from this mod_xxx component, then it's deemed to have subtypes.
|
||||
// If there is only 1 item, but it's not a reference to the core content item for the module, then it's also deemed to
|
||||
// have subtypes.
|
||||
$count = 0;
|
||||
foreach ($metadata as $key => $moduledata) {
|
||||
if (preg_match('/^'.$modname.':/', $key)) {
|
||||
if ('mod_'.$modname === $moduledata->componentname) {
|
||||
$count ++;
|
||||
}
|
||||
}
|
||||
if ($count > 1) {
|
||||
return true;
|
||||
} else {
|
||||
// Get the single item.
|
||||
$itemmetadata = $metadata[array_search('mod_' . $modname, array_column($metadata, 'componentname'))];
|
||||
$urlbase = new \moodle_url('/course/mod.php', ['id' => $this->get_course()->id]);
|
||||
$referenceurl = new \moodle_url($urlbase, ['add' => $modname]);
|
||||
if ($referenceurl->out(false) != $itemmetadata->link) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-98
@@ -621,102 +621,6 @@ function set_section_visible($courseid, $sectionnumber, $visibility) {
|
||||
return $resourcestotoggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all metadata for the requested modules
|
||||
*
|
||||
* @param object $course The Course
|
||||
* @param array $modnames An array containing the list of modules and their
|
||||
* names
|
||||
* @param int $sectionreturn The section to return to
|
||||
* @return array A list of stdClass objects containing metadata about each
|
||||
* module
|
||||
*/
|
||||
function get_module_metadata($course, $modnames, $sectionreturn = null) {
|
||||
global $OUTPUT;
|
||||
|
||||
// get_module_metadata will be called once per section on the page and courses may show
|
||||
// different modules to one another
|
||||
static $modlist = array();
|
||||
if (!isset($modlist[$course->id])) {
|
||||
$modlist[$course->id] = array();
|
||||
}
|
||||
|
||||
$return = array();
|
||||
$urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
|
||||
if ($sectionreturn !== null) {
|
||||
$urlbase->param('sr', $sectionreturn);
|
||||
}
|
||||
foreach($modnames as $modname => $modnamestr) {
|
||||
if (!course_allowed_module($course, $modname)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($modlist[$course->id][$modname])) {
|
||||
// This module is already cached
|
||||
$return += $modlist[$course->id][$modname];
|
||||
continue;
|
||||
}
|
||||
$modlist[$course->id][$modname] = array();
|
||||
|
||||
// Create an object for a default representation of this module type in the activity chooser. It will be used
|
||||
// if module does not implement callback get_shortcuts() and it will also be passed to the callback if it exists.
|
||||
$defaultmodule = new stdClass();
|
||||
$defaultmodule->title = $modnamestr;
|
||||
$defaultmodule->name = $modname;
|
||||
$defaultmodule->link = new moodle_url($urlbase, array('add' => $modname));
|
||||
$defaultmodule->icon = $OUTPUT->pix_icon('icon', '', $defaultmodule->name, array('class' => 'icon'));
|
||||
$sm = get_string_manager();
|
||||
if ($sm->string_exists('modulename_help', $modname)) {
|
||||
$defaultmodule->help = get_string('modulename_help', $modname);
|
||||
if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
|
||||
$link = get_string('modulename_link', $modname);
|
||||
$linktext = get_string('morehelp');
|
||||
$defaultmodule->help .= html_writer::tag('div',
|
||||
$OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
|
||||
}
|
||||
}
|
||||
$defaultmodule->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
|
||||
|
||||
// Each module can implement callback modulename_get_shortcuts() in its lib.php and return the list
|
||||
// of elements to be added to activity chooser.
|
||||
$items = component_callback($modname, 'get_shortcuts', array($defaultmodule), null);
|
||||
if ($items !== null) {
|
||||
foreach ($items as $item) {
|
||||
// Add all items to the return array. All items must have different links, use them as a key in the return array.
|
||||
if (!isset($item->archetype)) {
|
||||
$item->archetype = $defaultmodule->archetype;
|
||||
}
|
||||
if (!isset($item->icon)) {
|
||||
$item->icon = $defaultmodule->icon;
|
||||
}
|
||||
// If plugin returned the only one item with the same link as default item - cache it as $modname,
|
||||
// otherwise append the link url to the module name.
|
||||
$item->name = (count($items) == 1 &&
|
||||
$item->link->out() === $defaultmodule->link->out()) ? $modname : $modname . ':' . $item->link;
|
||||
|
||||
// If the module provides the helptext property, append it to the help text to match the look and feel
|
||||
// of the default course modules.
|
||||
if (isset($item->help) && isset($item->helplink)) {
|
||||
$linktext = get_string('morehelp');
|
||||
$item->help .= html_writer::tag('div',
|
||||
$OUTPUT->doc_link($item->helplink, $linktext, true), array('class' => 'helpdoclink'));
|
||||
}
|
||||
$modlist[$course->id][$modname][$item->name] = $item;
|
||||
}
|
||||
$return += $modlist[$course->id][$modname];
|
||||
// If get_shortcuts() callback is defined, the default module action is not added.
|
||||
// It is a responsibility of the callback to add it to the return value unless it is not needed.
|
||||
continue;
|
||||
}
|
||||
|
||||
// The callback get_shortcuts() was not found, use the default item for the activity chooser.
|
||||
$modlist[$course->id][$modname][$modname] = $defaultmodule;
|
||||
$return[$modname] = $defaultmodule;
|
||||
}
|
||||
|
||||
core_collator::asort_objects_by_property($return, 'title');
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the course category context for the category with id $categoryid, except
|
||||
* that if $categoryid is 0, return the system context.
|
||||
@@ -2155,9 +2059,12 @@ function course_format_name ($course,$max=100) {
|
||||
* Is the user allowed to add this type of module to this course?
|
||||
* @param object $course the course settings. Only $course->id is used.
|
||||
* @param string $modname the module name. E.g. 'forum' or 'quiz'.
|
||||
* @param \stdClass $user the user to check, defaults to the global user if not provided.
|
||||
* @return bool whether the current user is allowed to add this type of module to this course.
|
||||
*/
|
||||
function course_allowed_module($course, $modname) {
|
||||
function course_allowed_module($course, $modname, \stdClass $user = null) {
|
||||
global $USER;
|
||||
$user = $user ?? $USER;
|
||||
if (is_numeric($modname)) {
|
||||
throw new coding_exception('Function course_allowed_module no longer
|
||||
supports numeric module ids. Please update your code to pass the module name.');
|
||||
@@ -2179,7 +2086,7 @@ function course_allowed_module($course, $modname) {
|
||||
}
|
||||
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
return has_capability($capability, $coursecontext);
|
||||
return has_capability($capability, $coursecontext, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
-7
@@ -268,20 +268,28 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
function course_section_add_cm_control($course, $section, $sectionreturn = null, $displayoptions = array()) {
|
||||
global $CFG;
|
||||
global $CFG, $PAGE, $USER;
|
||||
|
||||
$vertical = !empty($displayoptions['inblock']);
|
||||
|
||||
// check to see if user can add menus and there are modules to add
|
||||
// Check to see if user can add menus.
|
||||
if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
|
||||
|| !$this->page->user_is_editing()
|
||||
|| !($modnames = get_module_types_names()) || empty($modnames)) {
|
||||
|| !$this->page->user_is_editing()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Retrieve all modules with associated metadata
|
||||
$modules = get_module_metadata($course, $modnames, $sectionreturn);
|
||||
$urlparams = array('section' => $section);
|
||||
$contentitemservice = \core_course\local\factory\content_item_service_factory::get_content_item_service();
|
||||
$urlparams = ['section' => $section];
|
||||
if (!is_null($sectionreturn)) {
|
||||
$urlparams['sr'] = $sectionreturn;
|
||||
}
|
||||
$modules = $contentitemservice->get_content_items_for_user_in_course($USER, $course, $urlparams);
|
||||
|
||||
// Return if there are no content items to add.
|
||||
if (empty($modules)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We'll sort resources and activities into two lists
|
||||
$activities = array(MOD_CLASS_ACTIVITY => array(), MOD_CLASS_RESOURCE => array());
|
||||
@@ -294,7 +302,7 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
// System modules cannot be added by user, do not add to dropdown.
|
||||
continue;
|
||||
}
|
||||
$link = $module->link->out(true, $urlparams);
|
||||
$link = $module->link;
|
||||
$activities[$activityclass][$link] = $module->title;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,33 +21,34 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"label": "Option name",
|
||||
"description": "Option description",
|
||||
"urls": {
|
||||
"addoption": "http://addoptionurl.com"
|
||||
},
|
||||
"icon": "<img class='icon' src='http://urltooptionicon'>"
|
||||
"id": 125,
|
||||
"name": "assign",
|
||||
"title": "Assignment",
|
||||
"link": "http://yourmoodle/modedit.php?id=x&itemtype=y",
|
||||
"icon": "<img class='icon' src='http://urltooptionicon'>",
|
||||
"help": "This is a description of the assignment activity",
|
||||
"archetype": 0,
|
||||
"componentname": "mod_assign",
|
||||
"favourite": 1
|
||||
}
|
||||
}}
|
||||
<div class="optionsummary" tabindex="-1" data-region="chooser-option-summary-container" aria-labelledby="optionsummary_label" aria-describedby="optionsumary_desc">
|
||||
<div class="content text-left mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
|
||||
<div class="heading mb-4">
|
||||
<h5 id="optionsummary_label">
|
||||
{{#icon}}
|
||||
{{>core/pix_icon}}
|
||||
{{/icon}}
|
||||
{{label}}
|
||||
{{{icon}}}
|
||||
{{title}}
|
||||
</h5>
|
||||
</div>
|
||||
<div id="optionsumary_desc" class="description" data-region="summary-description" tabindex="0">
|
||||
{{{description}}}
|
||||
{{{help}}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions fixed-bottom w-100 d-flex justify-content-between position-absolute py-3 px-4" data-region="chooser-option-summary-actions-container">
|
||||
<button data-action="close-chooser-option-summary" class="closeoptionsummary btn btn-secondary" tabindex="0" data-modname="{{modulename}}">
|
||||
<button data-action="close-chooser-option-summary" class="closeoptionsummary btn btn-secondary" tabindex="0" data-modname="{{componentname}}_{{link}}">
|
||||
{{#str}} back {{/str}}
|
||||
</button>
|
||||
<a href="{{urls.addoption}}" title="{{#str}} addnew, moodle, {{label}} {{/str}}" data-action="add-chooser-option" class="addoption btn btn-primary" tabindex="0">
|
||||
<a href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" data-action="add-chooser-option" class="addoption btn btn-primary" tabindex="0">
|
||||
{{#str}} add {{/str}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -29,20 +29,18 @@
|
||||
"icon": "<img class='icon' src='http://urltooptionicon'>"
|
||||
}
|
||||
}}
|
||||
<div role="menuitem" tabindex="-1" aria-label="{{label}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-modname="{{modulename}}">
|
||||
<div role="menuitem" tabindex="-1" aria-label="{{label}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-modname="{{componentname}}_{{link}}">
|
||||
<div class="optioninfo w-100" data-region="chooser-option-info-container">
|
||||
<a class="d-block" href="{{urls.addoption}}" title="{{#str}} addnew, moodle, {{label}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
|
||||
<a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
|
||||
<span class="optionicon d-block">
|
||||
{{#icon}}
|
||||
{{>core/pix_icon}}
|
||||
{{/icon}}
|
||||
{{{icon}}}
|
||||
</span>
|
||||
<span class="optionname d-block">{{label}}</span>
|
||||
<span class="optionname d-block">{{title}}</span>
|
||||
</a>
|
||||
<div class="optionactions btn-group" role="group" data-region="chooser-option-actions-container">
|
||||
<button class="btn btn-icon icon-no-margin icon-size-3 m-0 optionaction" data-action="show-option-summary" tabindex="-1">
|
||||
<span aria-hidden="true">{{#pix}} docs, core {{/pix}}</span>
|
||||
<span class="sr-only">{{#str}} informationformodule, core_course, {{label}} {{/str}}</span>
|
||||
<span class="sr-only">{{#str}} informationformodule, core_course, {{title}} {{/str}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the test class for the caching_content_item_readonly_repository class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\core_course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\repository\content_item_readonly_repository;
|
||||
use core_course\local\repository\caching_content_item_readonly_repository;
|
||||
|
||||
/**
|
||||
* The test class for the caching_content_item_readonly_repository class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class caching_content_item_readonly_repository_testcase extends \advanced_testcase {
|
||||
/**
|
||||
* Test verifying that content items are cached and returned from the cache in subsequent same-request calls.
|
||||
*/
|
||||
public function test_find_all_for_course() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cir = new content_item_readonly_repository();
|
||||
$ccir = new caching_content_item_readonly_repository(\cache::make('core', 'user_course_content_items'), $cir);
|
||||
|
||||
// Get the content items using both the live and the caching repos.
|
||||
$items = $cir->find_all_for_course($course, $user);
|
||||
$cacheditems = $ccir->find_all_for_course($course, $user);
|
||||
$itemsfiltered = array_filter($items, function($item) {
|
||||
return $item->get_component_name() == 'mod_assign';
|
||||
});
|
||||
$cacheditemsfiltered = array_filter($cacheditems, function($item) {
|
||||
return $item->get_component_name() == 'mod_assign';
|
||||
});
|
||||
|
||||
// Verify the assign module is in both result sets.
|
||||
$module = $DB->get_record('modules', ['name' => 'assign']);
|
||||
$this->assertEquals($module->name, $itemsfiltered[0]->get_name());
|
||||
$this->assertEquals($module->name, $cacheditemsfiltered[0]->get_name());
|
||||
|
||||
// Hide a module and get the content items again.
|
||||
$DB->set_field("modules", "visible", "0", ["id" => $module->id]);
|
||||
$items = $cir->find_all_for_course($course, $user);
|
||||
$cacheditems = $ccir->find_all_for_course($course, $user);
|
||||
$itemsfiltered = array_filter($items, function($item) {
|
||||
return $item->get_component_name() == 'mod_assign';
|
||||
});
|
||||
$cacheditemsfiltered = array_filter($cacheditems, function($item) {
|
||||
return $item->get_component_name() == 'mod_assign';
|
||||
});
|
||||
|
||||
// The caching repo should return the same list, while the live repo will return the updated list.
|
||||
$this->assertEquals($module->name, $cacheditemsfiltered[0]->get_name());
|
||||
$this->assertEmpty($itemsfiltered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the test class for the content_item_readonly_repository class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\core_course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\entity\content_item;
|
||||
use core_course\local\repository\content_item_readonly_repository;
|
||||
|
||||
/**
|
||||
* The test class for the content_item_readonly_repository class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item_readonly_repository_testcase extends \advanced_testcase {
|
||||
/**
|
||||
* Test the repository method, find_all_for_course().
|
||||
*/
|
||||
public function test_find_all_for_course() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cir = new content_item_readonly_repository();
|
||||
|
||||
$items = $cir->find_all_for_course($course, $user);
|
||||
foreach ($items as $key => $item) {
|
||||
$this->assertInstanceOf(content_item::class, $item);
|
||||
$this->assertEquals($course->id, $item->get_link()->param('id'));
|
||||
$this->assertNotNull($item->get_link()->param('add'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that content items for hidden modules are not returned.
|
||||
*/
|
||||
public function test_find_all_for_course_hidden_module() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cir = new content_item_readonly_repository();
|
||||
|
||||
// Hide a module.
|
||||
$module = $DB->get_record('modules', ['id' => 1]);
|
||||
$DB->set_field("modules", "visible", "0", ["id" => $module->id]);
|
||||
|
||||
$items = $cir->find_all_for_course($course, $user);
|
||||
$this->assertArrayNotHasKey($module->name, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that all content items can be fetched, even those which require certain caps when in a course.
|
||||
*/
|
||||
public function test_find_all() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// We'll compare our results to those which are course-specific.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
assign_capability('mod/lti:addmanualinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
||||
$cir = new content_item_readonly_repository();
|
||||
|
||||
// Course specific - lti won't be returned as the user doesn't have the required cap.
|
||||
$forcourse = $cir->find_all_for_course($course, $user);
|
||||
$forcourse = array_filter($forcourse, function($contentitem) {
|
||||
return $contentitem->get_name() === 'lti';
|
||||
});
|
||||
$this->assertEmpty($forcourse);
|
||||
|
||||
// All - all items are returned, including lti.
|
||||
$all = $cir->find_all();
|
||||
$all = array_filter($all, function($contentitem) {
|
||||
return $contentitem->get_name() === 'lti';
|
||||
});
|
||||
$this->assertCount(1, $all);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains tests for the \core_course\local\entity\content_item class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\core_course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\entity\content_item;
|
||||
use core_course\local\entity\lang_string_title;
|
||||
use core_course\local\entity\string_title;
|
||||
|
||||
/**
|
||||
* Tests for the \core_course\local\entity\content_item class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_item_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the content_item class.
|
||||
*/
|
||||
public function test_content_item() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$contentitem = new content_item(22, 'Item name', new lang_string_title('modulename', 'mod_assign'),
|
||||
new \moodle_url('mod_edit.php'), '<img src="test">', 'Description of the module', MOD_ARCHETYPE_RESOURCE, 'mod_page');
|
||||
|
||||
$this->assertEquals(22, $contentitem->get_id());
|
||||
$this->assertEquals('Item name', $contentitem->get_name());
|
||||
$this->assertEquals('Assignment', $contentitem->get_title()->get_value());
|
||||
$this->assertEquals(new \moodle_url('mod_edit.php'), $contentitem->get_link());
|
||||
$this->assertEquals('<img src="test">', $contentitem->get_icon());
|
||||
$this->assertEquals('Description of the module', $contentitem->get_help());
|
||||
$this->assertEquals(MOD_ARCHETYPE_RESOURCE, $contentitem->get_archetype());
|
||||
$this->assertEquals('mod_page', $contentitem->get_component_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that plugins can return custom titles for a content item.
|
||||
*/
|
||||
public function test_content_item_custom_string_title() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$contentitem = new content_item(22, 'Item name', new string_title('My custom string'),
|
||||
new \moodle_url('mod_edit.php'), '<img src="test">', 'Description of the module', MOD_ARCHETYPE_RESOURCE, 'mod_page');
|
||||
|
||||
$this->assertEquals('My custom string', $contentitem->get_title()->get_value());
|
||||
}
|
||||
}
|
||||
@@ -6923,4 +6923,26 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->setAdminUser();
|
||||
$this->assertTrue($request2->can_approve());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the course allowed module method.
|
||||
*/
|
||||
public function test_course_allowed_module() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
|
||||
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
||||
|
||||
// Global user (teacher) has no permissions in this course.
|
||||
$this->setUser($teacher);
|
||||
$this->assertFalse(course_allowed_module($course, 'assign'));
|
||||
|
||||
// Manager has permissions.
|
||||
$this->assertTrue(course_allowed_module($course, 'assign', $manager));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the tests for the course_content_item_exporter class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\core_course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\exporters\course_content_item_exporter;
|
||||
use core_course\local\repository\content_item_readonly_repository;
|
||||
|
||||
/**
|
||||
* The tests for the course_content_item_exporter class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exporters_course_content_item_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test confirming a content_item can be exported for a course.
|
||||
*/
|
||||
public function test_export_course_content_item() {
|
||||
$this->resetAfterTest();
|
||||
global $PAGE;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cir = new content_item_readonly_repository();
|
||||
$contentitems = $cir->find_all_for_course($course, $user);
|
||||
$contentitem = array_shift($contentitems);
|
||||
|
||||
$ciexporter = new course_content_item_exporter($contentitem, ['context' => \context_course::instance($course->id)]);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$exporteditem = $ciexporter->export($renderer);
|
||||
|
||||
$this->assertObjectHasAttribute('id', $exporteditem);
|
||||
$this->assertEquals($exporteditem->id, $contentitem->get_id());
|
||||
$this->assertObjectHasAttribute('name', $exporteditem);
|
||||
$this->assertEquals($exporteditem->name, $contentitem->get_name());
|
||||
$this->assertObjectHasAttribute('title', $exporteditem);
|
||||
$this->assertEquals($exporteditem->title, $contentitem->get_title()->get_value());
|
||||
$this->assertObjectHasAttribute('link', $exporteditem);
|
||||
$this->assertEquals($exporteditem->link, $contentitem->get_link()->out(false));
|
||||
$this->assertObjectHasAttribute('icon', $exporteditem);
|
||||
$this->assertEquals($exporteditem->icon, $contentitem->get_icon());
|
||||
$this->assertObjectHasAttribute('help', $exporteditem);
|
||||
$this->assertEquals($exporteditem->help, $contentitem->get_help());
|
||||
$this->assertObjectHasAttribute('archetype', $exporteditem);
|
||||
$this->assertEquals($exporteditem->archetype, $contentitem->get_archetype());
|
||||
$this->assertObjectHasAttribute('componentname', $exporteditem);
|
||||
$this->assertEquals($exporteditem->componentname, $contentitem->get_component_name());
|
||||
$this->assertObjectHasAttribute('legacyitem', $exporteditem);
|
||||
$this->assertFalse($exporteditem->legacyitem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that legacy items (with id of -1) are exported correctly.
|
||||
*/
|
||||
public function test_export_course_content_item_legacy() {
|
||||
$this->resetAfterTest();
|
||||
global $PAGE;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
$contentitem = new \core_course\local\entity\content_item(
|
||||
-1,
|
||||
'test_name',
|
||||
new \core_course\local\entity\string_title('test_title'),
|
||||
new \moodle_url(''),
|
||||
'',
|
||||
'',
|
||||
MOD_ARCHETYPE_OTHER,
|
||||
'core_test'
|
||||
);
|
||||
|
||||
$ciexporter = new course_content_item_exporter($contentitem, ['context' => \context_course::instance($course->id)]);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$exporteditem = $ciexporter->export($renderer);
|
||||
|
||||
$this->assertObjectHasAttribute('id', $exporteditem);
|
||||
$this->assertEquals($exporteditem->id, $contentitem->get_id());
|
||||
$this->assertObjectHasAttribute('name', $exporteditem);
|
||||
$this->assertEquals($exporteditem->name, $contentitem->get_name());
|
||||
$this->assertObjectHasAttribute('title', $exporteditem);
|
||||
$this->assertEquals($exporteditem->title, $contentitem->get_title()->get_value());
|
||||
$this->assertObjectHasAttribute('link', $exporteditem);
|
||||
$this->assertEquals($exporteditem->link, $contentitem->get_link()->out(false));
|
||||
$this->assertObjectHasAttribute('icon', $exporteditem);
|
||||
$this->assertEquals($exporteditem->icon, $contentitem->get_icon());
|
||||
$this->assertObjectHasAttribute('help', $exporteditem);
|
||||
$this->assertEquals($exporteditem->help, $contentitem->get_help());
|
||||
$this->assertObjectHasAttribute('archetype', $exporteditem);
|
||||
$this->assertEquals($exporteditem->archetype, $contentitem->get_archetype());
|
||||
$this->assertObjectHasAttribute('componentname', $exporteditem);
|
||||
$this->assertEquals($exporteditem->componentname, $contentitem->get_component_name());
|
||||
// Most important, is this a legacy item?
|
||||
$this->assertObjectHasAttribute('legacyitem', $exporteditem);
|
||||
$this->assertTrue($exporteditem->legacyitem);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the tests for the course_content_items_exporter class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\core_course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_course\local\exporters\course_content_items_exporter;
|
||||
use core_course\local\repository\content_item_readonly_repository;
|
||||
|
||||
/**
|
||||
* The tests for the course_content_items_exporter class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exporters_course_content_items_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test confirming the collection of content_items can be exported for a course.
|
||||
*/
|
||||
public function test_export_course_content_items() {
|
||||
$this->resetAfterTest();
|
||||
global $PAGE;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cir = new content_item_readonly_repository();
|
||||
$contentitems = $cir->find_all_for_course($course, $user);
|
||||
|
||||
$ciexporter = new course_content_items_exporter($contentitems, ['context' => \context_course::instance($course->id)]);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$exportedcontentitems = $ciexporter->export($renderer);
|
||||
|
||||
$this->assertObjectHasAttribute('content_items', $exportedcontentitems);
|
||||
foreach ($exportedcontentitems->content_items as $key => $dto) {
|
||||
$this->assertObjectHasAttribute('id', $dto);
|
||||
$this->assertObjectHasAttribute('name', $dto);
|
||||
$this->assertObjectHasAttribute('title', $dto);
|
||||
$this->assertObjectHasAttribute('link', $dto);
|
||||
$this->assertObjectHasAttribute('icon', $dto);
|
||||
$this->assertObjectHasAttribute('help', $dto);
|
||||
$this->assertObjectHasAttribute('archetype', $dto);
|
||||
$this->assertObjectHasAttribute('componentname', $dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3051,37 +3051,123 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetch_modules_activity_chooser
|
||||
* Verify that content items can be added to user favourites.
|
||||
*/
|
||||
public function test_fetch_modules_activity_chooser() {
|
||||
global $OUTPUT;
|
||||
public function test_add_content_item_to_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$this->setUser($user);
|
||||
|
||||
// Log in as Admin.
|
||||
$this->setAdminUser();
|
||||
// Using the internal API, confirm that no items are set as favourites for the user.
|
||||
$contentitemservice = new \core_course\local\service\content_item_service(
|
||||
new \core_course\local\repository\content_item_readonly_repository()
|
||||
);
|
||||
$contentitems = $contentitemservice->get_all_content_items($user);
|
||||
$favourited = array_filter($contentitems, function($contentitem) {
|
||||
return $contentitem->favourite == true;
|
||||
});
|
||||
$this->assertCount(0, $favourited);
|
||||
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
// Using the external API, favourite a content item for the user.
|
||||
$assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))];
|
||||
$contentitem = core_course_external::add_content_item_to_user_favourites('mod_assign', $assign->id, $user->id);
|
||||
$contentitem = external_api::clean_returnvalue(core_course_external::add_content_item_to_user_favourites_returns(),
|
||||
$contentitem);
|
||||
|
||||
// Fetch course modules.
|
||||
$result = core_course_external::fetch_modules_activity_chooser($course1->id);
|
||||
$result = external_api::clean_returnvalue(core_course_external::fetch_modules_activity_chooser_returns(), $result);
|
||||
// Check for 0 warnings.
|
||||
$this->assertEquals(0, count($result['warnings']));
|
||||
// Check we have the right number of standard modules.
|
||||
$this->assertEquals(21, count($result['allmodules']));
|
||||
// Verify the returned item is a favourite.
|
||||
$this->assertTrue($contentitem['favourite']);
|
||||
|
||||
$coursecontext = context_course::instance($course1->id);
|
||||
$modnames = get_module_types_names();
|
||||
$modules = get_module_metadata($course1, $modnames, null);
|
||||
$related = [
|
||||
'context' => $coursecontext
|
||||
];
|
||||
// Export the module chooser data.
|
||||
$modchooserdata = new \core_course\external\course_module_chooser_exporter($modules, $related);
|
||||
$formatteddata = $modchooserdata->export($OUTPUT)->options;
|
||||
// Using the internal API, confirm we see a single favourite item.
|
||||
$contentitems = $contentitemservice->get_all_content_items($user);
|
||||
$favourited = array_values(array_filter($contentitems, function($contentitem) {
|
||||
return $contentitem->favourite == true;
|
||||
}));
|
||||
$this->assertCount(1, $favourited);
|
||||
$this->assertEquals('assign', $favourited[0]->name);
|
||||
}
|
||||
|
||||
// Check if the webservice returns exactly what the exporter defines.
|
||||
$this->assertEquals($formatteddata, $result['allmodules']);
|
||||
/**
|
||||
* Verify that content items can be removed from user favourites.
|
||||
*/
|
||||
public function test_remove_content_item_from_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$this->setUser($user);
|
||||
|
||||
// Using the internal API, set a favourite for the user.
|
||||
$contentitemservice = new \core_course\local\service\content_item_service(
|
||||
new \core_course\local\repository\content_item_readonly_repository()
|
||||
);
|
||||
$contentitems = $contentitemservice->get_all_content_items($user);
|
||||
$assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))];
|
||||
$contentitemservice->add_to_user_favourites($user, $assign->componentname, $assign->id);
|
||||
|
||||
$contentitems = $contentitemservice->get_all_content_items($user);
|
||||
$favourited = array_filter($contentitems, function($contentitem) {
|
||||
return $contentitem->favourite == true;
|
||||
});
|
||||
$this->assertCount(1, $favourited);
|
||||
|
||||
// Now, verify the external API can remove the favourite.
|
||||
$contentitem = core_course_external::remove_content_item_from_user_favourites('mod_assign', $assign->id);
|
||||
$contentitem = external_api::clean_returnvalue(core_course_external::remove_content_item_from_user_favourites_returns(),
|
||||
$contentitem);
|
||||
|
||||
// Verify the returned item is a favourite.
|
||||
$this->assertFalse($contentitem['favourite']);
|
||||
|
||||
// Using the internal API, confirm we see no favourite items.
|
||||
$contentitems = $contentitemservice->get_all_content_items($user);
|
||||
$favourited = array_filter($contentitems, function($contentitem) {
|
||||
return $contentitem->favourite == true;
|
||||
});
|
||||
$this->assertCount(0, $favourited);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the web service returning course content items for inclusion in activity choosers, etc.
|
||||
*/
|
||||
public function test_get_course_content_items() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
// Fetch available content items as the editing teacher.
|
||||
$this->setUser($user);
|
||||
$result = core_course_external::get_course_content_items($course->id);
|
||||
$result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result);
|
||||
|
||||
$contentitemservice = new \core_course\local\service\content_item_service(
|
||||
new \core_course\local\repository\content_item_readonly_repository()
|
||||
);
|
||||
|
||||
// Check if the webservice returns exactly what the service defines, albeit in array form.
|
||||
$serviceitemsasarray = array_map(function($item) {
|
||||
return (array) $item;
|
||||
}, $contentitemservice->get_content_items_for_user_in_course($user, $course));
|
||||
|
||||
$this->assertEquals($serviceitemsasarray, $result['content_items']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the web service returning course content items, specifically in case where the user can't manage activities.
|
||||
*/
|
||||
public function test_get_course_content_items_no_permission_to_manage() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$user = self::getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
// Fetch available content items as a student, who won't have the permission to manage activities.
|
||||
$this->setUser($user);
|
||||
$result = core_course_external::get_course_content_items($course->id);
|
||||
$result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result);
|
||||
|
||||
$this->assertEmpty($result['content_items']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the tests for the content_item_service class.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tests\course;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_course\local\service\content_item_service;
|
||||
use \core_course\local\repository\content_item_readonly_repository;
|
||||
|
||||
/**
|
||||
* The tests for the content_item_service class.
|
||||
*
|
||||
* @copyright 2020 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class services_content_item_service_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test confirming that content items are returned by the service.
|
||||
*/
|
||||
public function test_get_content_items_for_user_in_course_basic() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$contentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
|
||||
foreach ($contentitems as $key => $contentitem) {
|
||||
$this->assertObjectHasAttribute('id', $contentitem);
|
||||
$this->assertObjectHasAttribute('name', $contentitem);
|
||||
$this->assertObjectHasAttribute('title', $contentitem);
|
||||
$this->assertObjectHasAttribute('link', $contentitem);
|
||||
$this->assertObjectHasAttribute('icon', $contentitem);
|
||||
$this->assertObjectHasAttribute('help', $contentitem);
|
||||
$this->assertObjectHasAttribute('archetype', $contentitem);
|
||||
$this->assertObjectHasAttribute('componentname', $contentitem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that access control is performed when asking the service to return content items for a user in a course.
|
||||
*/
|
||||
public function test_get_content_items_for_user_in_course_permissions() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
// No cap override, so assign should be returned.
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$contentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$this->assertContains('assign', array_column($contentitems, 'name'));
|
||||
|
||||
// Override the capability 'mod/assign:addinstance' for the 'editing teacher' role.
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
||||
|
||||
$contentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$this->assertArrayNotHasKey('assign', $contentitems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that params can be added to the content item's link.
|
||||
*/
|
||||
public function test_get_content_item_for_user_in_course_link_params() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$contentitems = $cis->get_content_items_for_user_in_course($user, $course, ['sr' => 7]);
|
||||
|
||||
foreach ($contentitems as $item) {
|
||||
$this->assertStringContainsString('sr=7', $item->link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that all content items can be fetched irrespective of permissions.
|
||||
*/
|
||||
public function test_get_all_content_items() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$allcontentitems = $cis->get_all_content_items($user);
|
||||
$coursecontentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
|
||||
// The call to get_all_content_items() should return the same items as for the course,
|
||||
// given the user in an editing teacher and can add manual lti instances.
|
||||
$this->assertEquals(array_column($allcontentitems, 'name'), array_column($coursecontentitems, 'name'));
|
||||
|
||||
// Now removing the cap 'mod/lti:addinstance'. This will restrict those items returned by the course-specific method.
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
assign_capability('mod/lti:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
||||
|
||||
// Verify that all items, including lti, are still returned by the get_all_content_items() call.
|
||||
$allcontentitems = $cis->get_all_content_items($user);
|
||||
$coursecontentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$this->assertNotContains('lti', array_column($coursecontentitems, 'name'));
|
||||
$this->assertContains('lti', array_column($allcontentitems, 'name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that a content item can be added to a user's favourites.
|
||||
*/
|
||||
public function test_add_to_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
|
||||
// Grab a the assign content item, which we'll favourite for the user.
|
||||
$items = $cis->get_all_content_items($user);
|
||||
$assign = $items[array_search('assign', array_column($items, 'name'))];
|
||||
$contentitem = $cis->add_to_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Verify the exported result is marked as a favourite.
|
||||
$this->assertEquals('assign', $contentitem->name);
|
||||
$this->assertTrue($contentitem->favourite);
|
||||
|
||||
// Verify the item is marked as a favourite when returned from the other service methods.
|
||||
$allitems = $cis->get_all_content_items($user);
|
||||
$allitemsassign = $allitems[array_search('assign', array_column($allitems, 'name'))];
|
||||
|
||||
$courseitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$courseitemsassign = $courseitems[array_search('assign', array_column($courseitems, 'name'))];
|
||||
$this->assertTrue($allitemsassign->favourite);
|
||||
$this->assertTrue($courseitemsassign->favourite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that content items can be removed from a user's favourites.
|
||||
*/
|
||||
public function test_remove_from_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
|
||||
// Grab a the assign content item, which we'll favourite for the user.
|
||||
$items = $cis->get_all_content_items($user);
|
||||
$assign = $items[array_search('assign', array_column($items, 'name'))];
|
||||
$cis->add_to_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Now, remove the favourite, and verify it.
|
||||
$contentitem = $cis->remove_from_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Verify the exported result is not marked as a favourite.
|
||||
$this->assertEquals('assign', $contentitem->name);
|
||||
$this->assertFalse($contentitem->favourite);
|
||||
|
||||
// Verify the item is not marked as a favourite when returned from the other service methods.
|
||||
$allitems = $cis->get_all_content_items($user);
|
||||
$allitemsassign = $allitems[array_search('assign', array_column($allitems, 'name'))];
|
||||
$courseitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$courseitemsassign = $courseitems[array_search('assign', array_column($courseitems, 'name'))];
|
||||
$this->assertFalse($allitemsassign->favourite);
|
||||
$this->assertFalse($courseitemsassign->favourite);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
This files describes API changes in /course/*,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9 ===
|
||||
|
||||
* The function get_module_metadata is now deprecated. Please use \core_course\local\service\content_item_service instead.
|
||||
|
||||
=== 3.8 ===
|
||||
|
||||
* The following functions have been finally deprecated and can not be used any more:
|
||||
|
||||
@@ -78,7 +78,9 @@ $string['cachedef_string'] = 'Language string cache';
|
||||
$string['cachedef_tags'] = 'Tags collections and areas';
|
||||
$string['cachedef_temp_tables'] = 'Temporary tables cache';
|
||||
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
|
||||
$string['cachedef_user_favourite_course_content_items'] = 'User\'s favourite content items (activities, resources and their subtypes)';
|
||||
$string['cachedef_user_group_groupings'] = 'User\'s groupings and groups per course';
|
||||
$string['cachedef_user_course_content_items'] = 'User\'s content items (activities, resources and their subtypes) per course';
|
||||
$string['cachedef_yuimodules'] = 'YUI Module definitions';
|
||||
$string['cachelock_file_default'] = 'Default file locking';
|
||||
$string['cachestores'] = 'Cache stores';
|
||||
|
||||
@@ -62,6 +62,7 @@ $string['noteachinginfomessage'] = 'Hi {$a->userfirstname},
|
||||
$string['privacy:perpage'] = 'The number of courses to show per page.';
|
||||
$string['privacy:completionpath'] = 'Course completion';
|
||||
$string['privacy:favouritespath'] = 'Course starred information';
|
||||
$string['privacy:metadata:activityfavouritessummary'] = 'The course system contains information about which items from the activity chooser have been starred by the user.';
|
||||
$string['privacy:metadata:completionsummary'] = 'The course contains completion information about the user.';
|
||||
$string['privacy:metadata:favouritessummary'] = 'The course contains information relating to the course being starred by the user.';
|
||||
$string['studentsatriskincourse'] = 'Students at risk in {$a} course';
|
||||
|
||||
@@ -415,4 +415,16 @@ $definitions = array(
|
||||
'simplekeys' => false,
|
||||
'simpledata' => false,
|
||||
],
|
||||
|
||||
// The list of content items (activities, resources and their subtypes) that can be added to a course for a user.
|
||||
'user_course_content_items' => [
|
||||
'mode' => cache_store::MODE_REQUEST,
|
||||
'simplekeys' => true,
|
||||
],
|
||||
|
||||
// The list of favourited content items (activities, resources and their subtypes) for a user.
|
||||
'user_favourite_course_content_items' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
],
|
||||
);
|
||||
|
||||
+19
-3
@@ -629,11 +629,27 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_get_activity_picker_info' => array(
|
||||
'core_course_add_content_item_to_user_favourites' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'fetch_modules_activity_chooser',
|
||||
'methodname' => 'add_content_item_to_user_favourites',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Fetch all the module information for the activity picker',
|
||||
'description' => 'Adds a content item (activity, resource or their subtypes) to the favourites for the user.',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_remove_content_item_from_user_favourites' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'remove_content_item_from_user_favourites',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Removes a content item (activity, resource or their subtypes) from the favourites for the user.',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_get_course_content_items' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'get_course_content_items',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Fetch all the content items (activities, resources and their subtypes) for the activity picker',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
),
|
||||
|
||||
@@ -3307,3 +3307,102 @@ function report_insights_context_insights(\context $context) {
|
||||
|
||||
return \core_analytics\manager::cached_models_with_insights($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all metadata for the requested modules
|
||||
*
|
||||
* @deprecated since 3.9.
|
||||
* @param object $course The Course
|
||||
* @param array $modnames An array containing the list of modules and their
|
||||
* names
|
||||
* @param int $sectionreturn The section to return to
|
||||
* @return array A list of stdClass objects containing metadata about each
|
||||
* module
|
||||
*/
|
||||
function get_module_metadata($course, $modnames, $sectionreturn = null) {
|
||||
global $OUTPUT;
|
||||
|
||||
debugging('get_module_metadata is deprecated. Please use \core_course\local\service\content_item_service instead.');
|
||||
|
||||
// get_module_metadata will be called once per section on the page and courses may show
|
||||
// different modules to one another
|
||||
static $modlist = array();
|
||||
if (!isset($modlist[$course->id])) {
|
||||
$modlist[$course->id] = array();
|
||||
}
|
||||
|
||||
$return = array();
|
||||
$urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
|
||||
if ($sectionreturn !== null) {
|
||||
$urlbase->param('sr', $sectionreturn);
|
||||
}
|
||||
foreach($modnames as $modname => $modnamestr) {
|
||||
if (!course_allowed_module($course, $modname)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($modlist[$course->id][$modname])) {
|
||||
// This module is already cached
|
||||
$return += $modlist[$course->id][$modname];
|
||||
continue;
|
||||
}
|
||||
$modlist[$course->id][$modname] = array();
|
||||
|
||||
// Create an object for a default representation of this module type in the activity chooser. It will be used
|
||||
// if module does not implement callback get_shortcuts() and it will also be passed to the callback if it exists.
|
||||
$defaultmodule = new stdClass();
|
||||
$defaultmodule->title = $modnamestr;
|
||||
$defaultmodule->name = $modname;
|
||||
$defaultmodule->link = new moodle_url($urlbase, array('add' => $modname));
|
||||
$defaultmodule->icon = $OUTPUT->pix_icon('icon', '', $defaultmodule->name, array('class' => 'icon'));
|
||||
$sm = get_string_manager();
|
||||
if ($sm->string_exists('modulename_help', $modname)) {
|
||||
$defaultmodule->help = get_string('modulename_help', $modname);
|
||||
if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
|
||||
$link = get_string('modulename_link', $modname);
|
||||
$linktext = get_string('morehelp');
|
||||
$defaultmodule->help .= html_writer::tag('div',
|
||||
$OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
|
||||
}
|
||||
}
|
||||
$defaultmodule->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
|
||||
|
||||
// Each module can implement callback modulename_get_shortcuts() in its lib.php and return the list
|
||||
// of elements to be added to activity chooser.
|
||||
$items = component_callback($modname, 'get_shortcuts', array($defaultmodule), null);
|
||||
if ($items !== null) {
|
||||
foreach ($items as $item) {
|
||||
// Add all items to the return array. All items must have different links, use them as a key in the return array.
|
||||
if (!isset($item->archetype)) {
|
||||
$item->archetype = $defaultmodule->archetype;
|
||||
}
|
||||
if (!isset($item->icon)) {
|
||||
$item->icon = $defaultmodule->icon;
|
||||
}
|
||||
// If plugin returned the only one item with the same link as default item - cache it as $modname,
|
||||
// otherwise append the link url to the module name.
|
||||
$item->name = (count($items) == 1 &&
|
||||
$item->link->out() === $defaultmodule->link->out()) ? $modname : $modname . ':' . $item->link;
|
||||
|
||||
// If the module provides the helptext property, append it to the help text to match the look and feel
|
||||
// of the default course modules.
|
||||
if (isset($item->help) && isset($item->helplink)) {
|
||||
$linktext = get_string('morehelp');
|
||||
$item->help .= html_writer::tag('div',
|
||||
$OUTPUT->doc_link($item->helplink, $linktext, true), array('class' => 'helpdoclink'));
|
||||
}
|
||||
$modlist[$course->id][$modname][$item->name] = $item;
|
||||
}
|
||||
$return += $modlist[$course->id][$modname];
|
||||
// If get_shortcuts() callback is defined, the default module action is not added.
|
||||
// It is a responsibility of the callback to add it to the return value unless it is not needed.
|
||||
continue;
|
||||
}
|
||||
|
||||
// The callback get_shortcuts() was not found, use the default item for the activity chooser.
|
||||
$modlist[$course->id][$modname][$modname] = $defaultmodule;
|
||||
$return[$modname] = $defaultmodule;
|
||||
}
|
||||
|
||||
core_collator::asort_objects_by_property($return, 'title');
|
||||
return $return;
|
||||
}
|
||||
|
||||
+121
@@ -208,6 +208,8 @@ function lti_delete_instance($id) {
|
||||
* Return aliases of this activity. LTI should have an alias for each configured tool type
|
||||
* This is so you can add an external tool types directly to the activity chooser
|
||||
*
|
||||
* @deprecated since 3.9
|
||||
* @todo MDL-68011 This is to be moved from here to deprecatedlib.php in Moodle 4.3
|
||||
* @param stdClass $defaultitem default item that would be added to the activity chooser if this callback was not present.
|
||||
* It has properties: archetype, name, title, help, icon, link
|
||||
* @return array An array of aliases for this activity. Each element is an object with same list of properties as $defaultitem,
|
||||
@@ -235,6 +237,125 @@ function lti_get_shortcuts($defaultitem) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preconfigured tools which are configured for inclusion in the activity picker.
|
||||
*
|
||||
* @param \core_course\local\entity\content_item $defaultmodulecontentitem reference to the content item for the LTI module.
|
||||
* @param \stdClass $user the user object, to use for cap checks if desired.
|
||||
* @param stdClass $course the course to scope items to.
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
function lti_get_course_content_items(\core_course\local\entity\content_item $defaultmodulecontentitem, \stdClass $user,
|
||||
\stdClass $course) {
|
||||
global $CFG, $OUTPUT;
|
||||
require_once($CFG->dirroot.'/mod/lti/locallib.php');
|
||||
|
||||
$types = [];
|
||||
|
||||
// The 'External tool' entry (the main module content item), should always take the id of 1.
|
||||
if (has_capability('mod/lti:addmanualinstance', context_course::instance($course->id), $user)) {
|
||||
$types = [new \core_course\local\entity\content_item(
|
||||
1,
|
||||
$defaultmodulecontentitem->get_name(),
|
||||
$defaultmodulecontentitem->get_title(),
|
||||
$defaultmodulecontentitem->get_link(),
|
||||
$defaultmodulecontentitem->get_icon(),
|
||||
$defaultmodulecontentitem->get_help(),
|
||||
$defaultmodulecontentitem->get_archetype(),
|
||||
$defaultmodulecontentitem->get_component_name()
|
||||
)];
|
||||
}
|
||||
|
||||
// Other, preconfigured tools take their own id + 1, so we'll never clash with the module's entry.
|
||||
$preconfiguredtools = lti_get_configured_types($course->id, $defaultmodulecontentitem->get_link()->param('sr'));
|
||||
foreach ($preconfiguredtools as $preconfiguredtool) {
|
||||
|
||||
// Append the help link to the help text.
|
||||
if (isset($preconfiguredtool->help)) {
|
||||
if (isset($preconfiguredtool->helplink)) {
|
||||
$linktext = get_string('morehelp');
|
||||
$preconfiguredtool->help .= html_writer::tag('div',
|
||||
$OUTPUT->doc_link($preconfiguredtool->helplink, $linktext, true), ['class' => 'helpdoclink']);
|
||||
}
|
||||
} else {
|
||||
$preconfiguredtool->help = '';
|
||||
}
|
||||
|
||||
$types[] = new \core_course\local\entity\content_item(
|
||||
$preconfiguredtool->id + 1,
|
||||
$preconfiguredtool->name,
|
||||
new \core_course\local\entity\string_title($preconfiguredtool->title),
|
||||
$preconfiguredtool->link,
|
||||
$preconfiguredtool->icon,
|
||||
$preconfiguredtool->help,
|
||||
$defaultmodulecontentitem->get_archetype(),
|
||||
$defaultmodulecontentitem->get_component_name()
|
||||
);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all content items which can be added to any course.
|
||||
*
|
||||
* @param \core_course\local\entity\content_item $defaultmodulecontentitem
|
||||
* @return array the array of content items.
|
||||
*/
|
||||
function mod_lti_get_all_content_items(\core_course\local\entity\content_item $defaultmodulecontentitem): array {
|
||||
global $OUTPUT, $CFG;
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php'); // For access to constants.
|
||||
|
||||
// The 'External tool' entry (the main module content item), should always take the id of 1.
|
||||
$types = [new \core_course\local\entity\content_item(
|
||||
1,
|
||||
$defaultmodulecontentitem->get_name(),
|
||||
$defaultmodulecontentitem->get_title(),
|
||||
$defaultmodulecontentitem->get_link(),
|
||||
$defaultmodulecontentitem->get_icon(),
|
||||
$defaultmodulecontentitem->get_help(),
|
||||
$defaultmodulecontentitem->get_archetype(),
|
||||
$defaultmodulecontentitem->get_component_name()
|
||||
)];
|
||||
|
||||
foreach (lti_get_lti_types() as $ltitype) {
|
||||
if ($ltitype->coursevisible != LTI_COURSEVISIBLE_ACTIVITYCHOOSER) {
|
||||
continue;
|
||||
}
|
||||
$type = new stdClass();
|
||||
$type->id = $ltitype->id;
|
||||
$type->modclass = MOD_CLASS_ACTIVITY;
|
||||
$type->name = 'lti_type_' . $ltitype->id;
|
||||
// Clean the name. We don't want tags here.
|
||||
$type->title = clean_param($ltitype->name, PARAM_NOTAGS);
|
||||
$trimmeddescription = trim($ltitype->description);
|
||||
$type->help = '';
|
||||
if ($trimmeddescription != '') {
|
||||
// Clean the description. We don't want tags here.
|
||||
$type->help = clean_param($trimmeddescription, PARAM_NOTAGS);
|
||||
$type->helplink = get_string('modulename_shortcut_link', 'lti');
|
||||
}
|
||||
if (empty($ltitype->icon)) {
|
||||
$type->icon = $OUTPUT->pix_icon('icon', '', 'lti', array('class' => 'icon'));
|
||||
} else {
|
||||
$type->icon = html_writer::empty_tag('img', array('src' => $ltitype->icon, 'alt' => $ltitype->name, 'class' => 'icon'));
|
||||
}
|
||||
$type->link = new moodle_url('/course/modedit.php', array('add' => 'lti', 'return' => 0, 'typeid' => $ltitype->id));
|
||||
|
||||
$types[] = new \core_course\local\entity\content_item(
|
||||
$type->id + 1,
|
||||
$type->name,
|
||||
new \core_course\local\entity\string_title($type->title),
|
||||
$type->link,
|
||||
$type->icon,
|
||||
$type->help,
|
||||
$defaultmodulecontentitem->get_archetype(),
|
||||
$defaultmodulecontentitem->get_component_name()
|
||||
);
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a coursemodule object, this function returns the extra
|
||||
* information needed to print this activity in various places.
|
||||
|
||||
@@ -2185,6 +2185,7 @@ function lti_get_configured_types($courseid, $sectionreturn = 0) {
|
||||
|
||||
foreach ($admintypes as $ltitype) {
|
||||
$type = new stdClass();
|
||||
$type->id = $ltitype->id;
|
||||
$type->modclass = MOD_CLASS_ACTIVITY;
|
||||
$type->name = 'lti_type_' . $ltitype->id;
|
||||
// Clean the name. We don't want tags here.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
This files describes API changes in /mod/lti/source/* - LTI source plugins,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9 ===
|
||||
|
||||
* The callback get_shortcuts() is now deprecated. Please use get_course_content_items and get_all_content_items instead.
|
||||
See source code examples in get_course_content_items() and get_all_content_items() in mod/lti/lib.php for details.
|
||||
|
||||
=== 3.1 ===
|
||||
|
||||
* Callback get_types() is deprecated, instead ltisource plugins can define callback get_shortcuts().
|
||||
|
||||
@@ -327,4 +327,130 @@ class mod_lti_lib_testcase extends advanced_testcase {
|
||||
|
||||
return calendar_event::create($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying the output of the lti_get_course_content_items and lti_get_all_content_items callbacks.
|
||||
*/
|
||||
public function test_content_item_callbacks() {
|
||||
$this->resetAfterTest();
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
|
||||
$admin = get_admin();
|
||||
$time = time();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$teacher2 = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
|
||||
|
||||
// Create some preconfigured tools.
|
||||
$sitetoolrecord = (object) [
|
||||
'name' => 'Site level tool which is available in the activity chooser',
|
||||
'baseurl' => 'http://example.com',
|
||||
'createdby' => $admin->id,
|
||||
'course' => SITEID,
|
||||
'ltiversion' => 'LTI-1p0',
|
||||
'timecreated' => $time,
|
||||
'timemodified' => $time,
|
||||
'state' => LTI_TOOL_STATE_CONFIGURED,
|
||||
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
|
||||
];
|
||||
$sitetoolrecordnonchooser = (object) [
|
||||
'name' => 'Site level tool which is NOT available in the course activity chooser',
|
||||
'baseurl' => 'http://example2.com',
|
||||
'createdby' => $admin->id,
|
||||
'course' => SITEID,
|
||||
'ltiversion' => 'LTI-1p0',
|
||||
'timecreated' => $time,
|
||||
'timemodified' => $time,
|
||||
'state' => LTI_TOOL_STATE_CONFIGURED,
|
||||
'coursevisible' => LTI_COURSEVISIBLE_PRECONFIGURED
|
||||
];
|
||||
$course1toolrecord = (object) [
|
||||
'name' => 'Course created tool which is available in the activity chooser',
|
||||
'baseurl' => 'http://example3.com',
|
||||
'createdby' => $teacher->id,
|
||||
'course' => $course->id,
|
||||
'ltiversion' => 'LTI-1p0',
|
||||
'timecreated' => $time,
|
||||
'timemodified' => $time,
|
||||
'state' => LTI_TOOL_STATE_CONFIGURED,
|
||||
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
|
||||
];
|
||||
$course2toolrecord = (object) [
|
||||
'name' => 'Course created tool which is available in the activity chooser',
|
||||
'baseurl' => 'http://example4.com',
|
||||
'createdby' => $teacher2->id,
|
||||
'course' => $course2->id,
|
||||
'ltiversion' => 'LTI-1p0',
|
||||
'timecreated' => $time,
|
||||
'timemodified' => $time,
|
||||
'state' => LTI_TOOL_STATE_CONFIGURED,
|
||||
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
|
||||
];
|
||||
$tool1id = $DB->insert_record('lti_types', $sitetoolrecord);
|
||||
$tool2id = $DB->insert_record('lti_types', $sitetoolrecordnonchooser);
|
||||
$tool3id = $DB->insert_record('lti_types', $course1toolrecord);
|
||||
$tool4id = $DB->insert_record('lti_types', $course2toolrecord);
|
||||
$sitetoolrecord->id = $tool1id;
|
||||
$sitetoolrecordnonchooser->id = $tool2id;
|
||||
$course1toolrecord->id = $tool3id;
|
||||
$course2toolrecord->id = $tool4id;
|
||||
|
||||
$defaultmodulecontentitem = new \core_course\local\entity\content_item(
|
||||
'1',
|
||||
'default module content item',
|
||||
new \core_course\local\entity\string_title('Content item title'),
|
||||
new moodle_url(''),
|
||||
'icon',
|
||||
'Description of the module',
|
||||
MOD_ARCHETYPE_OTHER,
|
||||
'mod_lti'
|
||||
);
|
||||
|
||||
// The lti_get_lti_types_by_course method (used by the callbacks) assumes the global user.
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Teacher in course1 should be able to see the default module item ('external tool'),
|
||||
// the site preconfigured tool and the tool created in course1.
|
||||
$courseitems = lti_get_course_content_items($defaultmodulecontentitem, $teacher, $course);
|
||||
$this->assertCount(3, $courseitems);
|
||||
$ids = [];
|
||||
foreach ($courseitems as $item) {
|
||||
$ids[] = $item->get_id();
|
||||
}
|
||||
$this->assertContains(1, $ids);
|
||||
$this->assertContains($sitetoolrecord->id + 1, $ids);
|
||||
$this->assertContains($course1toolrecord->id + 1, $ids);
|
||||
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
|
||||
|
||||
// The content items for teacher2 in course2 include the default module content item ('external tool'),
|
||||
// the site preconfigured tool and the tool created in course2.
|
||||
$this->setUser($teacher2);
|
||||
$course2items = lti_get_course_content_items($defaultmodulecontentitem, $teacher2, $course2);
|
||||
$this->assertCount(3, $course2items);
|
||||
$ids = [];
|
||||
foreach ($course2items as $item) {
|
||||
$ids[] = $item->get_id();
|
||||
}
|
||||
$this->assertContains(1, $ids);
|
||||
$this->assertContains($sitetoolrecord->id + 1, $ids);
|
||||
$this->assertContains($course2toolrecord->id + 1, $ids);
|
||||
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
|
||||
|
||||
// When fetching all content items, we expect to see all items available in activity choosers (in any course),
|
||||
// plus the default module content item ('external tool').
|
||||
$this->setAdminUser();
|
||||
$allitems = mod_lti_get_all_content_items($defaultmodulecontentitem);
|
||||
$this->assertCount(4, $allitems);
|
||||
$ids = [];
|
||||
foreach ($allitems as $item) {
|
||||
$ids[] = $item->get_id();
|
||||
}
|
||||
$this->assertContains(1, $ids);
|
||||
$this->assertContains($sitetoolrecord->id + 1, $ids);
|
||||
$this->assertContains($course1toolrecord->id + 1, $ids);
|
||||
$this->assertContains($course2toolrecord->id + 1, $ids);
|
||||
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
This files describes API changes in /mod/* - activity modules,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9 ===
|
||||
|
||||
* The callback get_shortcuts() is now deprecated. Please use get_course_content_items and get_all_content_items instead.
|
||||
See source code examples in get_course_content_items() and get_all_content_items() in mod/lti/lib.php for details.
|
||||
|
||||
=== 3.8 ===
|
||||
|
||||
* The final deprecation of xxx_print_overview() callback means that this function will no longer be called.
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2020021400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2020021400.03; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '3.9dev (Build: 20200214)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user