first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,86 @@
<?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/>.
/**
* Activity Chooser footer data class.
*
* @package core
* @subpackage course
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\local\entity;
/**
* A class to represent the Activity Chooser footer data.
*
* @package core
* @subpackage course
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_chooser_footer {
/** @var string $footerjspath The path to the plugin JS file to dynamically import later. */
protected $footerjspath;
/** @var string $footertemplate The rendered template for the footer. */
protected $footertemplate;
/** @var string $carouseltemplate The rendered template for the footer. */
protected $carouseltemplate;
/**
* Constructor method.
*
* @param string $footerjspath JS file to dynamically import later.
* @param string $footertemplate Footer template that has been rendered.
* @param string|null $carouseltemplate Carousel template that may have been rendered.
*/
public function __construct(string $footerjspath, string $footertemplate, ?string $carouseltemplate = '') {
$this->footerjspath = $footerjspath;
$this->footertemplate = $footertemplate;
$this->carouseltemplate = $carouseltemplate;
}
/**
* Get the footer JS file path for this plugin.
*
* @return string The JS file to call functions from.
*/
public function get_footer_js_file(): string {
return $this->footerjspath;
}
/**
* Get the footer rendered template for this plugin.
*
* @return string The template that has been rendered for the chooser footer.
*/
public function get_footer_template(): string {
return $this->footertemplate;
}
/**
* Get the carousel rendered template for this plugin.
*
* @return string The template that has been rendered for the chooser carousel.
*/
public function get_carousel_template(): string {
return $this->carouseltemplate;
}
}
@@ -0,0 +1,182 @@
<?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;
/** @var string $purpose the purpose type of this component. */
private $purpose;
/** @var bool $branded whether or not this component is branded. */
private $branded;
/**
* 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.
* @param string $purpose the purpose type of this component.
* @param bool $branded whether or not this item is branded.
*/
public function __construct(int $id, string $name, title $title, \moodle_url $link, string $icon, string $help,
int $archetype, string $componentname, string $purpose, bool $branded = false) {
$this->id = $id;
$this->name = $name;
$this->title = $title;
$this->link = $link;
$this->icon = $icon;
$this->help = $help;
$this->archetype = $archetype;
$this->componentname = $componentname;
$this->purpose = $purpose;
$this->branded = $branded;
}
/**
* 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;
}
/**
* Get purpose for this item.
*
* @return string
*/
public function get_purpose(): string {
return $this->purpose;
}
/**
* Whether this item is branded.
*
* @return bool true if this item is branded, false otherwise.
*/
public function is_branded(): bool {
return $this->branded;
}
}
@@ -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);
}
}
@@ -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;
}
}
+35
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;
}
@@ -0,0 +1,161 @@
<?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;
use core_course\local\service\content_item_service;
/**
* 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'],
'purpose' => ['type' => PARAM_TEXT, 'description' => 'The purpose of the component exposing the content item'],
'branded' => ['type' => PARAM_BOOL, 'description' => ' Whether this content item is branded or not'],
];
}
/**
* 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.'
],
'recommended' => ['type' => PARAM_BOOL, 'description' => 'Has this item been recommended'],
];
}
/**
* 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;
}
}
}
$recommended = false;
$itemtype = content_item_service::RECOMMENDATION_PREFIX . $this->contentitem->get_component_name();
if (isset($this->related['recommended'])) {
foreach ($this->related['recommended'] as $favobj) {
if ($favobj->itemtype === $itemtype && in_array($this->contentitem->get_id(), $favobj->ids)) {
$recommended = 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' => format_text($this->contentitem->get_help(), FORMAT_MARKDOWN),
'archetype' => $this->contentitem->get_archetype(),
'componentname' => $this->contentitem->get_component_name(),
'favourite' => $favourite,
'legacyitem' => ($this->contentitem->get_id() == -1),
'recommended' => $recommended,
'purpose' => $this->contentitem->get_purpose(),
'branded' => $this->contentitem->is_branded(),
];
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[]?',
'recommended' => '\stdClass[]?'
];
}
}
@@ -0,0 +1,108 @@
<?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'],
'recommended' => $this->related['recommended']
]
);
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[]?',
'recommended' => '\stdClass[]?'
];
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the service_factory, a locator for services for course content items.
*
* Services encapsulate the business logic, and any data manipulation code, and are what clients should interact with.
*
* @package core_course
* @copyright 2020 Adrian Greeve <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()
)
);
}
}
@@ -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();
}
}
@@ -0,0 +1,246 @@
<?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_component;
use core_course\local\entity\content_item;
use core_course\local\entity\lang_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');
$arialabel = get_string('morehelpaboutmodule', '', get_string('modulename', $modname));
$doclink = $OUTPUT->doc_link($link, $linktext, true, ['aria-label' => $arialabel]);
$help .= \html_writer::tag('div', $doclink, ['class' => 'helpdoclink']);
}
}
return $help;
}
/**
* 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;
}
/**
* 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, $CFG;
// 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) {
// Exclude modules if the code doesn't exist.
if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
continue;
}
// 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);
$purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
$isbranded = component_callback('mod_' . $mod->name, 'is_branded', [], false);
$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('monologo', '', $mod->name, ['class' => 'icon activityicon']),
$help,
$archetype,
'mod_' . $mod->name,
$purpose,
$isbranded,
);
$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, $CFG;
// 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) {
// Exclude modules if the code doesn't exist.
if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
continue;
}
// 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);
$purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
$isbranded = component_callback('mod_' . $mod->name, 'is_branded', [], false);
$icon = 'monologo';
// Quick check for monologo icons.
// Plugins that don't have monologo icons will be displayed as is and CSS filter will not be applied.
$hasmonologoicons = core_component::has_monologo_icon('mod', $mod->name);
$iconclass = '';
if (!$hasmonologoicons) {
$iconclass = 'nofilter';
}
$contentitem = new content_item(
$mod->id,
$mod->name,
new lang_string_title("modulename", $mod->name),
new \moodle_url('/course/mod.php', ['id' => $course->id, 'add' => $mod->name]),
$OUTPUT->pix_icon($icon, '', $mod->name, ['class' => "activityicon $iconclass"]),
$help,
$archetype,
'mod_' . $mod->name,
$purpose,
$isbranded,
);
$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 {
// Callback was not found, so just use the default module content item.
$return[] = $contentitem;
}
}
return $return;
}
}
@@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the interface content_item_readonly_repository_interface, defining operations for readonly content item repositories.
*
* This interface is not considered a published interface and serves to govern internal, local repository objects only.
* All calling code should use instances of the service classes, and should not interact with repositories directly.
*
* @package core
* @subpackage course
* @copyright 2020 Jake Dallimore <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;
}
@@ -0,0 +1,386 @@
<?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;
/** string the component for this favourite. */
public const COMPONENT = 'core_course';
/** string the favourite prefix itemtype in the favourites table. */
public const FAVOURITE_PREFIX = 'contentitem_';
/** string the recommendation prefix itemtype in the favourites table. */
public const RECOMMENDATION_PREFIX = 'recommend_';
/** string the cache name for recommendations. */
public const RECOMMENDATION_CACHE = 'recommendation_favourite_course_content_items';
/**
* 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;
}
$favourites = $this->get_content_favourites(self::FAVOURITE_PREFIX, \context_user::instance($user->id));
$favcache->set($key, $favourites);
return $favourites;
}
/**
* Returns an array of objects representing recommended 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.
*
* @return array
*/
private function get_recommendations(): array {
global $CFG;
$recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
$key = $CFG->siteguest;
$favmods = $recommendationcache->get($key);
if ($favmods !== false) {
return $favmods;
}
// Make sure the guest user exists in the database.
if (!\core_user::get_user($CFG->siteguest)) {
throw new \coding_exception('The guest user does not exist in the database.');
}
// Make sure the guest user context exists.
if (!$guestusercontext = \context_user::instance($CFG->siteguest, false)) {
throw new \coding_exception('The guest user context does not exist.');
}
$favourites = $this->get_content_favourites(self::RECOMMENDATION_PREFIX, $guestusercontext);
$recommendationcache->set($CFG->siteguest, $favourites);
return $favourites;
}
/**
* Gets content favourites from the favourites system depending on the area.
*
* @param string $prefix Prefix for the item type.
* @param \context_user $usercontext User context for the favourite
* @return array An array of favourite objects.
*/
private function get_content_favourites(string $prefix, \context_user $usercontext): array {
// 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[] = $prefix . '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) {
try {
if (component_callback_exists($subpluginname, 'get_course_content_items')) {
$itemtypes[] = $prefix . $subpluginname;
}
} catch (\moodle_exception $e) {
debugging('Cannot get_course_content_items: ' . $e->getMessage(), DEBUG_DEVELOPER);
}
}
}
$ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
$favourites = [];
$favs = $ufservice->find_all_favourites(self::COMPONENT, $itemtypes);
$favsreduced = array_reduce($favs, function($carry, $item) {
$carry[$item->itemtype][$item->itemid] = 0;
return $carry;
}, []);
foreach ($itemtypes as $type) {
$favourites[] = (object) [
'itemtype' => $type,
'ids' => isset($favsreduced[$type]) ? array_keys($favsreduced[$type]) : []
];
}
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 {
$allcontentitems = $this->repository->find_all();
return $this->export_content_items($user, $allcontentitems);
}
/**
* Get content items which name matches a certain pattern and may be added to courses,
* irrespective of course caps, for site admin views, etc.
*
* @param \stdClass $user The user object.
* @param string $pattern The search pattern.
* @return array The array of exported content items.
*/
public function get_content_items_by_name_pattern(\stdClass $user, string $pattern): array {
$allcontentitems = $this->repository->find_all();
$filteredcontentitems = array_filter($allcontentitems, function($contentitem) use ($pattern) {
return preg_match("/$pattern/i", $contentitem->get_title()->get_value());
});
return $this->export_content_items($user, $filteredcontentitems);
}
/**
* Export content items.
*
* @param \stdClass $user The user object.
* @param array $contentitems The content items array.
* @return array The array of exported content items.
*/
private function export_content_items(\stdClass $user, $contentitems) {
global $PAGE;
// Export the objects to get the formatted objects for transfer/display.
$favourites = $this->get_favourite_content_items_for_user($user);
$recommendations = $this->get_recommendations();
$ciexporter = new course_content_items_exporter(
$contentitems,
[
'context' => \context_system::instance(),
'favouriteitems' => $favourites,
'recommended' => $recommendations
]
);
$exported = $ciexporter->export($PAGE->get_renderer('core'));
// Sort by title for return.
\core_collator::asort_objects_by_property($exported->content_items, 'title');
return array_values($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);
$recommended = $this->get_recommendations();
$ciexporter = new course_content_items_exporter(
$availablecontentitems,
[
'context' => \context_course::instance($course->id),
'favouriteitems' => $favourites,
'recommended' => $recommended
]
);
$exported = $ciexporter->export($PAGE->get_renderer('course'));
// Sort by title for return.
\core_collator::asort_objects_by_property($exported->content_items, 'title');
return array_values($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 = self::FAVOURITE_PREFIX . $componentname;
$ufservice->create_favourite(self::COMPONENT, $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 = self::FAVOURITE_PREFIX . $componentname;
$ufservice->delete_favourite(self::COMPONENT, $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'))];
}
/**
* Toggle an activity to being recommended or not.
*
* @param string $itemtype The component such as mod_assign, or assignsubmission_file
* @param int $itemid The id related to this component item.
* @return bool True on creating a favourite, false on deleting it.
*/
public function toggle_recommendation(string $itemtype, int $itemid): bool {
global $CFG;
$context = \context_system::instance();
$itemtype = self::RECOMMENDATION_PREFIX . $itemtype;
// Favourites are created using a user context. We'll use the site guest user ID as that should not change and there
// can be only one.
$usercontext = \context_user::instance($CFG->siteguest);
$recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
$favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext);
if ($favouritefactory->favourite_exists(self::COMPONENT, $itemtype, $itemid, $context)) {
$favouritefactory->delete_favourite(self::COMPONENT, $itemtype, $itemid, $context);
$result = $recommendationcache->delete($CFG->siteguest);
return false;
} else {
$favouritefactory->create_favourite(self::COMPONENT, $itemtype, $itemid, $context);
$result = $recommendationcache->delete($CFG->siteguest);
return true;
}
}
}