Merge branch 'MDL-67585-master-4' of git://github.com/snake/moodle

This commit is contained in:
Jun Pataleta 2020-02-20 11:58:02 +08:00
commit 5a4c63aa4f
48 changed files with 2687 additions and 363 deletions

View File

@ -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+="&section="+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+="&section="+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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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"}

View File

@ -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 += '&section=' + id;
newData.content_items.forEach((module) => {
module.link += '&section=' + id;
});
return newData.allmodules;
return newData.content_items;
};
/**

View File

@ -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.

View File

@ -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,
},

View File

@ -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 <mihail@moodle.com>
* @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 <mihail@moodle.com>
* @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'
];
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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;
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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;
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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;
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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[]?'
];
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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[]?'
];
}
}

View File

@ -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 <adrian@moodle.com>
* @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 <adrian@moodle.com>
* @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()
)
);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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();
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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;
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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;
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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'))];
}
}

View File

@ -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;
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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());
}
}

View File

@ -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));
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}
}

View File

@ -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']);
}
}

View File

@ -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 <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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);
}
}

View File

@ -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:

View File

@ -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';

View File

@ -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';

View File

@ -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,
],
);

View File

@ -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,
),

View File

@ -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;
}

View File

@ -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.

View File

@ -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.

View File

@ -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().

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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