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
+400
View File
@@ -0,0 +1,400 @@
<?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/>.
/**
* Content manager class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank;
use core_text;
use stored_file;
use stdClass;
use coding_exception;
use context;
use moodle_url;
use core\event\contentbank_content_updated;
/**
* Content manager class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class content {
/**
* @var int Visibility value. Public content is visible to all users with access to the content bank of the
* appropriate context.
*/
public const VISIBILITY_PUBLIC = 1;
/**
* @var int Visibility value. Unlisted content is only visible to the author and to users with
* moodle/contentbank:viewunlistedcontent capability.
*/
public const VISIBILITY_UNLISTED = 2;
/** @var stdClass $content The content of the current instance. **/
protected $content = null;
/**
* Content bank constructor
*
* @param stdClass $record A contentbank_content record.
* @throws coding_exception If content type is not right.
*/
public function __construct(stdClass $record) {
// Content type should exist and be linked to plugin classname.
$classname = $record->contenttype.'\\content';
if (get_class($this) != $classname) {
throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
}
$typeclass = $record->contenttype.'\\contenttype';
if (!class_exists($typeclass)) {
throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
}
// A record with the id must exist in 'contentbank_content' table.
// To improve performance, we are only checking the id is set, but no querying the database.
if (!isset($record->id)) {
throw new coding_exception(get_string('invalidcontentid', 'error'));
}
$this->content = $record;
}
/**
* Returns $this->content.
*
* @return stdClass $this->content.
*/
public function get_content(): stdClass {
return $this->content;
}
/**
* Returns $this->content->contenttype.
*
* @return string $this->content->contenttype.
*/
public function get_content_type(): string {
return $this->content->contenttype;
}
/**
* Return the contenttype instance of this content.
*
* @return contenttype The content type instance
*/
public function get_content_type_instance(): contenttype {
$context = context::instance_by_id($this->content->contextid);
$contenttypeclass = "\\{$this->content->contenttype}\\contenttype";
return new $contenttypeclass($context);
}
/**
* Returns $this->content->timemodified.
*
* @return int $this->content->timemodified.
*/
public function get_timemodified(): int {
return $this->content->timemodified;
}
/**
* Updates content_bank table with information in $this->content.
*
* @return boolean True if the content has been succesfully updated. False otherwise.
* @throws \coding_exception if not loaded.
*/
public function update_content(): bool {
global $USER, $DB;
// A record with the id must exist in 'contentbank_content' table.
// To improve performance, we are only checking the id is set, but no querying the database.
if (!isset($this->content->id)) {
throw new coding_exception(get_string('invalidcontentid', 'error'));
}
$this->content->usermodified = $USER->id;
$this->content->timemodified = time();
$result = $DB->update_record('contentbank_content', $this->content);
if ($result) {
// Trigger an event for updating this content.
$event = contentbank_content_updated::create_from_record($this->content);
$event->trigger();
}
return $result;
}
/**
* Set a new name to the content.
*
* @param string $name The name of the content.
* @return bool True if the content has been succesfully updated. False otherwise.
* @throws \coding_exception if not loaded.
*/
public function set_name(string $name): bool {
$name = trim($name);
if ($name === '') {
return false;
}
// Clean name.
$name = clean_param($name, PARAM_TEXT);
if (core_text::strlen($name) > 255) {
$name = core_text::substr($name, 0, 255);
}
$oldname = $this->content->name;
$this->content->name = $name;
$updated = $this->update_content();
if (!$updated) {
$this->content->name = $oldname;
}
return $updated;
}
/**
* Returns the name of the content.
*
* @return string The name of the content.
*/
public function get_name(): string {
return $this->content->name;
}
/**
* Set a new contextid to the content.
*
* @param int $contextid The new contextid of the content.
* @return bool True if the content has been succesfully updated. False otherwise.
*/
public function set_contextid(int $contextid): bool {
if ($this->content->contextid == $contextid) {
return true;
}
$oldcontextid = $this->content->contextid;
$this->content->contextid = $contextid;
$updated = $this->update_content();
if ($updated) {
// Move files to new context
$fs = get_file_storage();
$fs->move_area_files_to_new_context($oldcontextid, $contextid, 'contentbank', 'public', $this->content->id);
} else {
$this->content->contextid = $oldcontextid;
}
return $updated;
}
/**
* Returns the contextid of the content.
*
* @return int The id of the content context.
*/
public function get_contextid(): string {
return $this->content->contextid;
}
/**
* Returns the content ID.
*
* @return int The content ID.
*/
public function get_id(): int {
return $this->content->id;
}
/**
* Change the content instanceid value.
*
* @param int $instanceid New instanceid for this content
* @return boolean True if the instanceid has been succesfully updated. False otherwise.
*/
public function set_instanceid(int $instanceid): bool {
$this->content->instanceid = $instanceid;
return $this->update_content();
}
/**
* Returns the $instanceid of this content.
*
* @return int contentbank instanceid
*/
public function get_instanceid(): int {
return $this->content->instanceid;
}
/**
* Change the content config values.
*
* @param string $configdata New config information for this content
* @return boolean True if the configdata has been succesfully updated. False otherwise.
*/
public function set_configdata(string $configdata): bool {
$this->content->configdata = $configdata;
return $this->update_content();
}
/**
* Return the content config values.
*
* @return mixed Config information for this content (json decoded)
*/
public function get_configdata() {
return $this->content->configdata;
}
/**
* Sets a new content visibility and saves it to database.
*
* @param int $visibility Must be self::PUBLIC or self::UNLISTED
* @return bool
* @throws coding_exception
*/
public function set_visibility(int $visibility): bool {
if (!in_array($visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED])) {
return false;
}
$this->content->visibility = $visibility;
return $this->update_content();
}
/**
* Return true if the content may be shown to other users in the content bank.
*
* @return boolean
*/
public function get_visibility(): int {
return $this->content->visibility;
}
/**
* Import a file as a valid content.
*
* By default, all content has a public file area to interact with the content bank
* repository. This method should be overridden by contentypes which does not simply
* upload to the public file area.
*
* If any, the method will return the final stored_file. This way it can be invoked
* as parent::import_file in case any plugin want to store the file in the public area
* and also parse it.
*
* @param stored_file $file File to store in the content file area.
* @return stored_file|null the stored content file or null if the file is discarted.
*/
public function import_file(stored_file $file): ?stored_file {
$originalfile = $this->get_file();
if ($originalfile) {
$originalfile->replace_file_with($file);
return $originalfile;
} else {
$fs = get_file_storage();
$filerecord = [
'contextid' => $this->get_contextid(),
'component' => 'contentbank',
'filearea' => 'public',
'itemid' => $this->get_id(),
'filepath' => '/',
'filename' => $file->get_filename(),
'timecreated' => time(),
];
return $fs->create_file_from_storedfile($filerecord, $file);
}
}
/**
* Returns the $file related to this content.
*
* @return stored_file File stored in content bank area related to the given itemid.
* @throws \coding_exception if not loaded.
*/
public function get_file(): ?stored_file {
$itemid = $this->get_id();
$fs = get_file_storage();
$files = $fs->get_area_files(
$this->content->contextid,
'contentbank',
'public',
$itemid,
'itemid, filepath, filename',
false
);
if (!empty($files)) {
$file = reset($files);
return $file;
}
return null;
}
/**
* Returns the places where the file associated to this content is used or an empty array if the content has no file.
*
* @return array of stored_file where current file content is used or empty array if it hasn't any file.
* @since 3.11
*/
public function get_uses(): ?array {
$references = [];
$file = $this->get_file();
if ($file != null) {
$fs = get_file_storage();
$references = $fs->get_references_by_storedfile($file);
}
return $references;
}
/**
* Returns the file url related to this content.
*
* @return string URL of the file stored in content bank area related to the given itemid.
* @throws \coding_exception if not loaded.
*/
public function get_file_url(): string {
if (!$file = $this->get_file()) {
return '';
}
$fileurl = moodle_url::make_pluginfile_url(
$this->content->contextid,
'contentbank',
'public',
$file->get_itemid(),
$file->get_filepath(),
$file->get_filename()
);
return $fileurl;
}
/**
* Returns user has access permission for the content itself (based on what plugin needs).
*
* @return bool True if content could be accessed. False otherwise.
*/
public function is_view_allowed(): bool {
// Plugins can overwrite this method in case they want to check something related to content properties.
global $USER;
$context = \context::instance_by_id($this->get_contextid());
return $USER->id == $this->content->usercreated ||
$this->get_visibility() == self::VISIBILITY_PUBLIC ||
has_capability('moodle/contentbank:viewunlistedcontent', $context);
}
}
+380
View File
@@ -0,0 +1,380 @@
<?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/>.
namespace core_contentbank;
use core_plugin_manager;
use stored_file;
use context;
/**
* Content bank class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contentbank {
/** @var array All the context levels allowed in the content bank */
private const ALLOWED_CONTEXT_LEVELS = [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE];
/** @var array Enabled content types. */
private $enabledcontenttypes = null;
/**
* Obtains the list of core_contentbank_content objects currently active.
*
* The list does not include players which are disabled.
*
* @return string[] Array of contentbank contenttypes.
*/
public function get_enabled_content_types(): array {
if (!is_null($this->enabledcontenttypes)) {
return $this->enabledcontenttypes;
}
$enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
$types = [];
foreach ($enabledtypes as $name) {
$contenttypeclassname = "\\contenttype_$name\\contenttype";
$contentclassname = "\\contenttype_$name\\content";
if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
$types[$contenttypeclassname] = $name;
}
}
return $this->enabledcontenttypes = $types;
}
/**
* Obtains an array of supported extensions by active plugins.
*
* @return array The array with all the extensions supported and the supporting plugin names.
*/
public function load_all_supported_extensions(): array {
$extensionscache = \cache::make('core', 'contentbank_enabled_extensions');
$supportedextensions = $extensionscache->get('enabled_extensions');
if ($supportedextensions === false) {
// Load all enabled extensions.
$supportedextensions = [];
foreach ($this->get_enabled_content_types() as $type) {
$classname = "\\contenttype_$type\\contenttype";
$contenttype = new $classname;
if ($contenttype->is_feature_supported($contenttype::CAN_UPLOAD)) {
$extensions = $contenttype->get_manageable_extensions();
foreach ($extensions as $extension) {
if (array_key_exists($extension, $supportedextensions)) {
$supportedextensions[$extension][] = $type;
} else {
$supportedextensions[$extension] = [$type];
}
}
}
}
$extensionscache->set('enabled_extensions', $supportedextensions);
}
return $supportedextensions;
}
/**
* Obtains an array of supported extensions in the given context.
*
* @param context $context Optional context to check (default null)
* @return array The array with all the extensions supported and the supporting plugin names.
*/
public function load_context_supported_extensions(context $context = null): array {
$extensionscache = \cache::make('core', 'contentbank_context_extensions');
$contextextensions = $extensionscache->get($context->id);
if ($contextextensions === false) {
$contextextensions = [];
$supportedextensions = $this->load_all_supported_extensions();
foreach ($supportedextensions as $extension => $types) {
foreach ($types as $type) {
$classname = "\\contenttype_$type\\contenttype";
$contenttype = new $classname($context);
if ($contenttype->can_upload()) {
$contextextensions[$extension] = $type;
break;
}
}
}
$extensionscache->set($context->id, $contextextensions);
}
return $contextextensions;
}
/**
* Obtains a string with all supported extensions by active plugins.
* Mainly to use as filepicker options parameter.
*
* @param context $context Optional context to check (default null)
* @return string A string with all the extensions supported.
*/
public function get_supported_extensions_as_string(context $context = null) {
$supported = $this->load_context_supported_extensions($context);
$extensions = array_keys($supported);
return implode(',', $extensions);
}
/**
* Returns the file extension for a file.
*
* @param string $filename The name of the file
* @return string The extension of the file
*/
public function get_extension(string $filename) {
$dot = strrpos($filename, '.');
if ($dot === false) {
return '';
}
return strtolower(substr($filename, $dot));
}
/**
* Get the first content bank plugin supports a file extension.
*
* @param string $extension Content file extension
* @param context $context $context Optional context to check (default null)
* @return string contenttype name supports the file extension or null if the extension is not supported by any allowed plugin.
*/
public function get_extension_supporter(string $extension, context $context = null): ?string {
$supporters = $this->load_context_supported_extensions($context);
if (array_key_exists($extension, $supporters)) {
return $supporters[$extension];
}
return null;
}
/**
* Find the contents with %$search% in the contextid defined.
* If contextid and search are empty, all contents are returned.
* In all the cases, only the contents for the enabled contentbank-type plugins are returned.
* No content-type permissions are validated here. It is the caller responsability to check that the user can access to them.
* The only validation done here is, for each content, a call to the method $content->is_view_allowed().
*
* @param string|null $search Optional string to search (for now it will search only into the name).
* @param int $contextid Optional contextid to search.
* @param array $contenttypenames Optional array with the list of content-type names to search.
* @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid.
*/
public function search_contents(?string $search = null, ?int $contextid = 0, ?array $contenttypenames = null): array {
global $DB;
$contents = [];
// Get only contents for enabled content-type plugins.
$contenttypes = [];
$enabledcontenttypes = $this->get_enabled_content_types();
foreach ($enabledcontenttypes as $contenttypename) {
if (empty($contenttypenames) || in_array($contenttypename, $contenttypenames)) {
$contenttypes[] = "contenttype_$contenttypename";
}
}
if (empty($contenttypes)) {
// Early return if there are no content-type plugins enabled.
return $contents;
}
list($sqlcontenttypes, $params) = $DB->get_in_or_equal($contenttypes, SQL_PARAMS_NAMED);
$sql = " contenttype $sqlcontenttypes ";
// Filter contents on this context (if defined).
if (!empty($contextid)) {
$params['contextid'] = $contextid;
$sql .= ' AND contextid = :contextid ';
}
// Search for contents having this string (if defined).
if (!empty($search)) {
$sql .= ' AND ' . $DB->sql_like('name', ':name', false, false);
$params['name'] = '%' . $DB->sql_like_escape($search) . '%';
}
$records = $DB->get_records_select('contentbank_content', $sql, $params, 'name ASC');
foreach ($records as $record) {
$content = $this->get_content_from_id($record->id);
if ($content->is_view_allowed()) {
$contents[] = $content;
}
}
return $contents;
}
/**
* Return all the context where a user has all the given capabilities.
*
* @param string $capability The capability the user needs to have.
* @param int|null $userid Optional userid. $USER by default.
* @return array Array of the courses and course categories where the user has the given capability.
*/
public function get_contexts_with_capabilities_by_user($capability = 'moodle/contentbank:access', $userid = null): array {
global $USER;
if (!$userid) {
$userid = $USER->id;
}
$categoriescache = \cache::make('core', 'contentbank_allowed_categories');
$coursescache = \cache::make('core', 'contentbank_allowed_courses');
$categories = $categoriescache->get($userid);
$courses = $coursescache->get($userid);
if ($categories === false || $courses === false) {
// Required fields for preloading the context record.
$contextfields = 'ctxid, ctxpath, ctxdepth, ctxlevel, ctxinstance, ctxlocked';
list($categories, $courses) = get_user_capability_contexts($capability, true, $userid, true,
"fullname, {$contextfields}", "name, {$contextfields}", 'fullname', 'name');
$categoriescache->set($userid, $categories);
$coursescache->set($userid, $courses);
}
return [$categories, $courses];
}
/**
* Create content from a file information.
*
* @param \context $context Context where to upload the file and content.
* @param int $userid Id of the user uploading the file.
* @param stored_file $file The file to get information from
* @return content
*/
public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content {
global $USER;
if (empty($userid)) {
$userid = $USER->id;
}
// Get the contenttype to manage given file's extension.
$filename = $file->get_filename();
$extension = $this->get_extension($filename);
$plugin = $this->get_extension_supporter($extension, $context);
$classname = '\\contenttype_'.$plugin.'\\contenttype';
$record = new \stdClass();
$record->name = $filename;
$record->usercreated = $userid;
$contentype = new $classname($context);
$content = $contentype->upload_content($file, $record);
$event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content());
$event->trigger();
return $content;
}
/**
* Delete content bank content by context.
*
* @param context $context The context to delete content from.
* @return bool
*/
public function delete_contents(context $context): bool {
global $DB;
$result = true;
$records = $DB->get_records('contentbank_content', ['contextid' => $context->id]);
foreach ($records as $record) {
$content = $this->get_content_from_id($record->id);
$contenttype = $content->get_content_type_instance();
if (!$contenttype->delete_content($content)) {
$result = false;
}
}
return $result;
}
/**
* Move content bank content from a context to another.
*
* @param context $from The context to get content from.
* @param context $to The context to move content to.
* @return bool
*/
public function move_contents(context $from, context $to): bool {
global $DB;
$result = true;
$records = $DB->get_records('contentbank_content', ['contextid' => $from->id]);
foreach ($records as $record) {
$content = $this->get_content_from_id($record->id);
$contenttype = $content->get_content_type_instance();
if (!$contenttype->move_content($content, $to)) {
$result = false;
}
}
return $result;
}
/**
* Get the list of content types that have the requested feature.
*
* @param string $feature Feature code e.g CAN_UPLOAD.
* @param null|\context $context Optional context to check the permission to use the feature.
* @param bool $enabled Whether check only the enabled content types or all of them.
*
* @return string[] List of content types where the user has permission to access the feature.
*/
public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array {
$contenttypes = [];
// Check enabled content types or all of them.
if ($enabled) {
$contenttypestocheck = $this->get_enabled_content_types();
} else {
$plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
foreach ($plugins as $plugin) {
$contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype";
$contenttypestocheck[$contenttypeclassname] = $plugin->name;
}
}
foreach ($contenttypestocheck as $classname => $name) {
$contenttype = new $classname($context);
// The method names that check the features permissions must follow the pattern can_feature.
if ($contenttype->{"can_$feature"}()) {
$contenttypes[$classname] = $name;
}
}
return $contenttypes;
}
/**
* Return a content class form a content id.
*
* @throws coding_exception if the ID is not valid or some class does no exists
* @param int $id the content id
* @return content the content class instance
*/
public function get_content_from_id(int $id): content {
global $DB;
$record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST);
$contentclass = "\\$record->contenttype\\content";
return new $contentclass($record);
}
/**
* Whether the context is allowed.
*
* @param context $context Context to check.
* @return bool
*/
public function is_context_allowed(context $context): bool {
return in_array($context->contextlevel, self::ALLOWED_CONTEXT_LEVELS);
}
}
+554
View File
@@ -0,0 +1,554 @@
<?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/>.
namespace core_contentbank;
use core\event\contentbank_content_created;
use core\event\contentbank_content_deleted;
use core\event\contentbank_content_viewed;
use stored_file;
use moodle_url;
/**
* Content type manager class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class contenttype {
/** @var string Constant representing whether the plugin implements uploading feature */
const CAN_UPLOAD = 'upload';
/** @var string Constant representing whether the plugin implements edition feature */
const CAN_EDIT = 'edit';
/**
* @var string Constant representing whether the plugin implements download feature
* @since Moodle 3.10
*/
const CAN_DOWNLOAD = 'download';
/**
* @var string Constant representing whether the plugin implements copy feature
* @since Moodle 4.3
*/
const CAN_COPY = 'copy';
/** @var \context This contenttype's context. **/
protected $context = null;
/**
* Content type constructor
*
* @param \context $context Optional context to check (default null)
*/
public function __construct(\context $context = null) {
if (empty($context)) {
$context = \context_system::instance();
}
$this->context = $context;
}
/**
* Fills content_bank table with appropiate information.
*
* @throws dml_exception A DML specific exception is thrown for any creation error.
* @param \stdClass $record An optional content record compatible object (default null)
* @return content Object with content bank information.
*/
public function create_content(\stdClass $record = null): content {
global $USER, $DB, $CFG;
$entry = new \stdClass();
if (isset($record->visibility)) {
$entry->visibility = $record->visibility;
} else {
$usercreated = $record->usercreated ?? $USER->id;
$entry->visibility = get_user_preferences('core_contentbank_visibility',
$CFG->defaultpreference_core_contentbank_visibility, $usercreated);
}
$entry->contenttype = $this->get_contenttype_name();
$entry->contextid = $this->context->id;
$entry->name = $record->name ?? '';
$entry->usercreated = $record->usercreated ?? $USER->id;
$entry->timecreated = time();
$entry->usermodified = $entry->usercreated;
$entry->timemodified = $entry->timecreated;
$entry->configdata = $record->configdata ?? '';
$entry->instanceid = $record->instanceid ?? 0;
$entry->id = $DB->insert_record('contentbank_content', $entry);
$classname = '\\'.$entry->contenttype.'\\content';
$content = new $classname($entry);
// Trigger an event for creating the content.
$event = contentbank_content_created::create_from_record($content->get_content());
$event->trigger();
return $content;
}
/**
* Create a new content from an uploaded file.
*
* @throws file_exception If file operations fail
* @throws dml_exception if the content creation fails
* @param stored_file $file the uploaded file
* @param \stdClass|null $record an optional content record
* @return content Object with content bank information.
*/
public function upload_content(stored_file $file, \stdClass $record = null): content {
if (empty($record)) {
$record = new \stdClass();
$record->name = $file->get_filename();
}
$content = $this->create_content($record);
try {
$content->import_file($file);
} catch (\moodle_exception $e) {
$this->delete_content($content);
throw new \moodle_exception($e->errorcode);
}
return $content;
}
/**
* Replace a content using an uploaded file.
*
* @throws file_exception If file operations fail
* @throws dml_exception if the content creation fails
* @param stored_file $file the uploaded file
* @param content $content the original content record
* @return content Object with the updated content bank information.
*/
public function replace_content(stored_file $file, content $content): content {
$content->import_file($file);
$content->update_content();
return $content;
}
/**
* Delete this content from the content_bank.
* This method can be overwritten by the plugins if they need to delete specific information.
*
* @param content $content The content to delete.
* @return boolean true if the content has been deleted; false otherwise.
*/
public function delete_content(content $content): bool {
global $DB;
// Delete the file if it exists.
if ($file = $content->get_file()) {
$file->delete();
}
// Delete the contentbank DB entry.
$result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
if ($result) {
// Trigger an event for deleting this content.
$record = $content->get_content();
$event = contentbank_content_deleted::create([
'objectid' => $content->get_id(),
'relateduserid' => $record->usercreated,
'context' => \context::instance_by_id($record->contextid),
'other' => [
'contenttype' => $content->get_content_type(),
'name' => $content->get_name()
]
]);
$event->add_record_snapshot('contentbank_content', $record);
$event->trigger();
}
return $result;
}
/**
* Rename this content from the content_bank.
* This method can be overwritten by the plugins if they need to change some other specific information.
*
* @param content $content The content to rename.
* @param string $name The name of the content.
* @return boolean true if the content has been renamed; false otherwise.
*/
public function rename_content(content $content, string $name): bool {
return $content->set_name($name);
}
/**
* Move content to another context.
* This method can be overwritten by the plugins if they need to change some other specific information.
*
* @param content $content The content to rename.
* @param \context $context The new context.
* @return boolean true if the content has been renamed; false otherwise.
*/
public function move_content(content $content, \context $context): bool {
return $content->set_contextid($context->id);
}
/**
* Returns the contenttype name of this content.
*
* @return string Content type of the current instance
*/
public function get_contenttype_name(): string {
$classname = get_class($this);
$contenttype = explode('\\', $classname);
return array_shift($contenttype);
}
/**
* Returns the plugin name of the current instance.
*
* @return string Plugin name of the current instance
*/
public function get_plugin_name(): string {
$contenttype = $this->get_contenttype_name();
$plugin = explode('_', $contenttype);
return array_pop($plugin);
}
/**
* Returns the URL where the content will be visualized.
*
* @param content $content The content to be displayed.
* @return string URL where to visualize the given content.
*/
public function get_view_url(content $content): string {
return new moodle_url('/contentbank/view.php', ['id' => $content->get_id()]);
}
/**
* Returns the HTML content to add to view.php visualizer.
*
* @param content $content The content to be displayed.
* @return string HTML code to include in view.php.
*/
public function get_view_content(content $content): string {
// Trigger an event for viewing this content.
$event = contentbank_content_viewed::create_from_record($content->get_content());
$event->trigger();
return '';
}
/**
* Returns the URL to download the content.
*
* @since Moodle 3.10
* @param content $content The content to be downloaded.
* @return string URL with the content to download.
*/
public function get_download_url(content $content): string {
$downloadurl = '';
$file = $content->get_file();
if (!empty($file)) {
$url = \moodle_url::make_pluginfile_url(
$file->get_contextid(),
$file->get_component(),
$file->get_filearea(),
$file->get_itemid(),
$file->get_filepath(),
$file->get_filename()
);
$downloadurl = $url->out(false);
}
return $downloadurl;
}
/**
* Returns the HTML code to render the icon for content bank contents.
*
* @param content $content The content to be displayed.
* @return string HTML code to render the icon
*/
public function get_icon(content $content): string {
global $OUTPUT;
return $OUTPUT->image_url('f/unknown')->out(false);
}
/**
* Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
*
* @return bool True if content could be accessed. False otherwise.
*/
final public function can_access(): bool {
$classname = 'contenttype/'.$this->get_plugin_name();
$capability = $classname.":access";
$hascapabilities = has_capability('moodle/contentbank:access', $this->context)
&& has_capability($capability, $this->context);
return $hascapabilities && $this->is_access_allowed();
}
/**
* Returns user has access capability for the content itself.
*
* @return bool True if content could be accessed. False otherwise.
*/
protected function is_access_allowed(): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns the user has permission to upload new content.
*
* @return bool True if content could be uploaded. False otherwise.
*/
final public function can_upload(): bool {
if (!$this->is_feature_supported(self::CAN_UPLOAD)) {
return false;
}
if (!$this->can_access()) {
return false;
}
$classname = 'contenttype/'.$this->get_plugin_name();
$uploadcap = $classname.':upload';
$hascapabilities = has_capability('moodle/contentbank:upload', $this->context)
&& has_capability($uploadcap, $this->context);
return $hascapabilities && $this->is_upload_allowed();
}
/**
* Returns plugin allows uploading.
*
* @return bool True if plugin allows uploading. False otherwise.
*/
protected function is_upload_allowed(): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Check if the user can delete this content.
*
* @param content $content The content to be deleted.
* @return bool True if content could be uploaded. False otherwise.
*/
final public function can_delete(content $content): bool {
global $USER;
if ($this->context->id != $content->get_content()->contextid) {
// The content has to have exactly the same context as this contenttype.
return false;
}
$hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
if ($content->get_content()->usercreated == $USER->id) {
// This content has been created by the current user; check if she can delete her content.
$hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
}
return $hascapability && $this->is_delete_allowed($content);
}
/**
* Returns if content allows deleting.
*
* @param content $content The content to be deleted.
* @return bool True if content allows uploading. False otherwise.
*/
protected function is_delete_allowed(content $content): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Check if the user can managed this content.
*
* @param content $content The content to be managed.
* @return bool True if content could be managed. False otherwise.
*/
final public function can_manage(content $content): bool {
global $USER;
if ($this->context->id != $content->get_content()->contextid) {
// The content has to have exactly the same context as this contenttype.
return false;
}
// Check main contentbank management permission.
$hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
if ($content->get_content()->usercreated == $USER->id) {
// This content has been created by the current user; check if they can manage their content.
$hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
}
return $hascapability && $this->is_manage_allowed($content);
}
/**
* Returns if content allows managing.
*
* @param content $content The content to be managed.
* @return bool True if content allows uploading. False otherwise.
*/
protected function is_manage_allowed(content $content): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns whether or not the user has permission to use the editor.
* This function will be called with the content to be edited as parameter,
* or null when is checking permission to create a new content using the editor.
*
* @param content $content The content to be edited or null when creating a new content.
* @return bool True if the user can edit content. False otherwise.
*/
final public function can_edit(?content $content = null): bool {
if (!$this->is_feature_supported(self::CAN_EDIT)) {
return false;
}
if (!$this->can_access()) {
return false;
}
if (!is_null($content) && !$this->can_manage($content)) {
return false;
}
$classname = 'contenttype/'.$this->get_plugin_name();
$editioncap = $classname.':useeditor';
$hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
return $hascapabilities && $this->is_edit_allowed($content);
}
/**
* Returns plugin allows edition.
*
* @param content $content The content to be edited.
* @return bool True if plugin allows edition. False otherwise.
*/
protected function is_edit_allowed(?content $content): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns whether or not the user has permission to download the content.
*
* @since Moodle 3.10
* @param content $content The content to be downloaded.
* @return bool True if the user can download the content. False otherwise.
*/
final public function can_download(content $content): bool {
if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
return false;
}
if (!$this->can_access()) {
return false;
}
$hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
return $hascapability && $this->is_download_allowed($content);
}
/**
* Returns whether or not the user has permission to copy the content.
*
* @since Moodle 4.3
* @param content $content The content to be copied.
* @return bool True if the user can copy the content. False otherwise.
*/
final public function can_copy(content $content): bool {
global $USER;
if (!$this->is_feature_supported(self::CAN_COPY)) {
return false;
}
if (!$this->can_access()) {
return false;
}
if (!$this->is_copy_allowed($content)) {
return false;
}
$hascapability = has_capability('moodle/contentbank:copyanycontent', $this->context);
if (!$hascapability && ($content->get_content()->usercreated == $USER->id)) {
$hascapability = has_capability('moodle/contentbank:copycontent', $this->context);
}
return $hascapability;
}
/**
* Returns plugin allows downloading.
*
* @since Moodle 3.10
* @param content $content The content to be downloaed.
* @return bool True if plugin allows downloading. False otherwise.
*/
protected function is_download_allowed(content $content): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns plugin allows copying.
*
* @since Moodle 4.3
* @param content $content The content to be copied.
* @return bool True if plugin allows copying. False otherwise.
*/
protected function is_copy_allowed(content $content): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns the plugin supports the feature.
*
* @param string $feature Feature code e.g CAN_UPLOAD
* @return bool True if content could be uploaded. False otherwise.
*/
final public function is_feature_supported(string $feature): bool {
return in_array($feature, $this->get_implemented_features());
}
/**
* Return an array of implemented features by the plugins.
*
* @return array
*/
abstract protected function get_implemented_features(): array;
/**
* Return an array of extensions the plugins could manage.
*
* @return array
*/
abstract public function get_manageable_extensions(): array;
/**
* Returns the list of different types of the given content type.
*
* A content type can have one or more options for creating content. This method will report all of them or only the content
* type itself if it has no other options.
*
* @return array An object for each type:
* - string typename: descriptive name of the type.
* - string typeeditorparams: params required by this content type editor.
* - url typeicon: this type icon.
*/
abstract public function get_contenttype_types(): array;
}
+145
View File
@@ -0,0 +1,145 @@
<?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/>.
namespace core_contentbank\external;
use core_contentbank\contentbank;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* This is the external method for copying a content.
*
* @package core_contentbank
* @copyright 2023 Daniel Neis Araujo
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class copy_content extends external_api {
/**
* copy_content parameters.
*
* @since Moodle 4.3
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'contentid' => new external_value(PARAM_INT, 'The content id to copy', VALUE_REQUIRED),
'name' => new external_value(PARAM_RAW, 'The new name for the content', VALUE_REQUIRED),
]
);
}
/**
* Copy content from the contentbank.
*
* @since Moodle 4.3
* @param int $contentid The content id to copy.
* @param string $name The new name.
* @return array Id of the new content; false and the warning, otherwise.
*/
public static function execute(int $contentid, string $name): array {
global $DB;
$id = 0;
$warnings = [];
$params = self::validate_parameters(self::execute_parameters(), [
'contentid' => $contentid,
'name' => $name,
]);
$params['name'] = clean_param($params['name'], PARAM_TEXT);
// If name is empty don't try to copy and return a more detailed message.
if (trim($params['name']) === '') {
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'emptynamenotallowed',
'message' => get_string('emptynamenotallowed', 'core_contentbank'),
];
} else {
try {
$record = $DB->get_record('contentbank_content', ['id' => $params['contentid']], '*', MUST_EXIST);
$cb = new contentbank();
$content = $cb->get_content_from_id($record->id);
$contenttype = $content->get_content_type_instance();
$context = \context::instance_by_id($record->contextid, MUST_EXIST);
self::validate_context($context);
// Check capability.
if ($contenttype->can_copy($content)) {
// This content can be copied.
$crecord = $content->get_content();
unset($crecord->id);
$crecord->name = $params['name'];
if ($content = $contenttype->create_content($crecord)) {
$fs = get_file_storage();
$files = $fs->get_area_files($context->id, 'contentbank', 'public', $params['contentid'], 'itemid, filepath,
filename', false);
if (!empty($files)) {
$file = reset($files);
$content->import_file($file);
}
$id = $content->get_id();
} else {
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'contentnotcopied',
'message' => get_string('contentnotcopied', 'core_contentbank'),
];
}
} else {
// The user has no permission to manage this content.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'nopermissiontomanage',
'message' => get_string('nopermissiontocopy', 'core_contentbank'),
];
}
} catch (\moodle_exception $e) {
// The content or the context don't exist.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'exception',
'message' => $e->getMessage(),
];
}
}
return [
'id' => $id,
'warnings' => $warnings,
];
}
/**
* copy_content return.
*
* @since Moodle 4.3
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'id' => new external_value(PARAM_INT, 'The id of the new content'),
'warnings' => new external_warnings(),
]);
}
}
+119
View File
@@ -0,0 +1,119 @@
<?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/>.
namespace core_contentbank\external;
use core_contentbank\contentbank;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* This is the external method for deleting a content.
*
* @package core_contentbank
* @since Moodle 3.9
* @copyright 2020 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_content extends external_api {
/**
* Parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'contentids' => new external_multiple_structure(
new external_value(PARAM_INT, 'The content id to delete', VALUE_REQUIRED)
)
]);
}
/**
* Delete content from the contentbank.
*
* @param array $contentids List of content ids to delete.
* @return array True if the content has been deleted; false and the warning, otherwise.
*/
public static function execute(array $contentids): array {
global $DB;
$result = false;
$warnings = [];
$params = self::validate_parameters(self::execute_parameters(), ['contentids' => $contentids]);
$cb = new contentbank();
foreach ($params['contentids'] as $contentid) {
try {
$record = $DB->get_record('contentbank_content', ['id' => $contentid], '*', MUST_EXIST);
$content = $cb->get_content_from_id($record->id);
$contenttype = $content->get_content_type_instance();
$context = \context::instance_by_id($record->contextid, MUST_EXIST);
self::validate_context($context);
// Check capability.
if ($contenttype->can_delete($content)) {
// This content can be deleted.
if (!$contenttype->delete_content($content)) {
$warnings[] = [
'item' => $contentid,
'warningcode' => 'contentnotdeleted',
'message' => get_string('contentnotdeleted', 'core_contentbank')
];
}
} else {
// The user has no permission to delete this content.
$warnings[] = [
'item' => $contentid,
'warningcode' => 'nopermissiontodelete',
'message' => get_string('nopermissiontodelete', 'core_contentbank')
];
}
} catch (\moodle_exception $e) {
// The content or the context don't exist.
$warnings[] = [
'item' => $contentid,
'warningcode' => 'exception',
'message' => $e->getMessage()
];
}
}
if (empty($warnings)) {
$result = true;
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Return.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
}
+132
View File
@@ -0,0 +1,132 @@
<?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/>.
namespace core_contentbank\external;
use core_contentbank\contentbank;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* This is the external method for renaming a content.
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class rename_content extends external_api {
/**
* rename_content parameters.
*
* @since Moodle 3.9
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'contentid' => new external_value(PARAM_INT, 'The content id to rename', VALUE_REQUIRED),
'name' => new external_value(PARAM_RAW, 'The new name for the content', VALUE_REQUIRED),
]
);
}
/**
* Rename content from the contentbank.
*
* @since Moodle 3.9
* @param int $contentid The content id to rename.
* @param string $name The new name.
* @return array True if the content has been renamed; false and the warning, otherwise.
*/
public static function execute(int $contentid, string $name): array {
global $DB;
$result = false;
$warnings = [];
$params = self::validate_parameters(self::execute_parameters(), [
'contentid' => $contentid,
'name' => $name,
]);
$params['name'] = clean_param($params['name'], PARAM_TEXT);
// If name is empty don't try to rename and return a more detailed message.
if (trim($params['name']) === '') {
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'emptynamenotallowed',
'message' => get_string('emptynamenotallowed', 'core_contentbank')
];
} else {
try {
$record = $DB->get_record('contentbank_content', ['id' => $params['contentid']], '*', MUST_EXIST);
$cb = new contentbank();
$content = $cb->get_content_from_id($record->id);
$contenttype = $content->get_content_type_instance();
$context = \context::instance_by_id($record->contextid, MUST_EXIST);
self::validate_context($context);
// Check capability.
if ($contenttype->can_manage($content)) {
// This content can be renamed.
if ($contenttype->rename_content($content, $params['name'])) {
$result = true;
} else {
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'contentnotrenamed',
'message' => get_string('contentnotrenamed', 'core_contentbank')
];
}
} else {
// The user has no permission to manage this content.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'nopermissiontomanage',
'message' => get_string('nopermissiontomanage', 'core_contentbank')
];
}
} catch (\moodle_exception $e) {
// The content or the context don't exist.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'exception',
'message' => $e->getMessage()
];
}
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* rename_content return.
*
* @since Moodle 3.9
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
}
+125
View File
@@ -0,0 +1,125 @@
<?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/>.
namespace core_contentbank\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* External API to set the visibility of content bank content.
*
* @package core_contentbank
* @copyright 2020 François Moreau
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set_content_visibility extends external_api {
/**
* set_content_visibility parameters.
*
* @since Moodle 3.11
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'contentid' => new external_value(PARAM_INT, 'The content id to rename', VALUE_REQUIRED),
'visibility' => new external_value(PARAM_INT, 'The new visibility for the content', VALUE_REQUIRED),
]
);
}
/**
* Set visibility of a content from the contentbank.
*
* @since Moodle 3.11
* @param int $contentid The content id to rename.
* @param int $visibility The new visibility.
* @return array
*/
public static function execute(int $contentid, int $visibility): array {
global $DB;
$result = false;
$warnings = [];
$params = self::validate_parameters(self::execute_parameters(), [
'contentid' => $contentid,
'visibility' => $visibility,
]);
try {
$record = $DB->get_record('contentbank_content', ['id' => $params['contentid']], '*', MUST_EXIST);
$contenttypeclass = "\\$record->contenttype\\contenttype";
if (class_exists($contenttypeclass)) {
$context = \context::instance_by_id($record->contextid, MUST_EXIST);
self::validate_context($context);
$contenttype = new $contenttypeclass($context);
$contentclass = "\\$record->contenttype\\content";
$content = new $contentclass($record);
// Check capability.
if ($contenttype->can_manage($content)) {
// This content's visibility can be changed.
if ($content->set_visibility($params['visibility'])) {
$result = true;
} else {
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'contentvisibilitynotset',
'message' => get_string('contentvisibilitynotset', 'core_contentbank')
];
}
} else {
// The user has no permission to manage this content.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'nopermissiontomanage',
'message' => get_string('nopermissiontomanage', 'core_contentbank')
];
}
}
} catch (\moodle_exception $e) {
// The content or the context don't exist.
$warnings[] = [
'item' => $params['contentid'],
'warningcode' => 'exception',
'message' => $e->getMessage()
];
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* set_content_visibility return.
*
* @since Moodle 3.11
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'The processing result'),
'warnings' => new external_warnings()
]);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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/>.
/**
* Provides {@see \core_contentbank\form\edit_content} class.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\form;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Defines the form for editing a content.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class edit_content extends moodleform {
/** @var int Context the content belongs to. */
protected $contextid;
/** @var string Content type plugin name. */
protected $plugin;
/** @var int Content id in the content bank. */
protected $id;
/**
* Constructor.
*
* @param string $action The action attribute for the form.
* @param array $customdata Data to set during instance creation.
* @param string $method Form method.
*/
public function __construct(string $action = null, array $customdata = null, string $method = 'post') {
parent::__construct($action, $customdata, $method);
$this->contextid = $customdata['contextid'];
$this->plugin = $customdata['plugin'];
$this->id = $customdata['id'];
$mform =& $this->_form;
$mform->addElement('hidden', 'contextid', $this->contextid);
$this->_form->setType('contextid', PARAM_INT);
$mform->addElement('hidden', 'plugin', $this->plugin);
$this->_form->setType('plugin', PARAM_PLUGIN);
$mform->addElement('hidden', 'id', $this->id);
$this->_form->setType('id', PARAM_INT);
}
/**
* Overrides formslib's add_action_buttons() method.
*
*
* @param bool $cancel
* @param string|null $submitlabel
*
* @return void
*/
public function add_action_buttons($cancel = true, $submitlabel = null): void {
if (is_null($submitlabel)) {
$submitlabel = get_string('save');
}
parent::add_action_buttons($cancel, $submitlabel);
}
}
+230
View File
@@ -0,0 +1,230 @@
<?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/>.
namespace core_contentbank\form;
use core\output\notification;
/**
* Upload files to content bank form
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class upload_files extends \core_form\dynamic_form {
/**
* Add elements to this form.
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'contextid');
$mform->setType('contextid', PARAM_INT);
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('filepicker', 'file', get_string('file', 'core_contentbank'), null, $this->get_options());
$mform->addHelpButton('file', 'file', 'core_contentbank');
$mform->addRule('file', null, 'required');
}
/**
* Validate incoming data.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
$errors = array();
$draftitemid = $data['file'];
$options = $this->get_options();
if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
$errors['file'] = get_string('userquotalimit', 'error');
}
return $errors;
}
/**
* Check if current user has access to this form, otherwise throw exception
*
* Sometimes permission check may depend on the action and/or id of the entity.
* If necessary, form data is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*/
protected function check_access_for_dynamic_submission(): void {
require_capability('moodle/contentbank:upload', $this->get_context_for_dynamic_submission());
// Check the context used by the content bank is allowed.
$cb = new \core_contentbank\contentbank();
if (!$cb->is_context_allowed($this->get_context_for_dynamic_submission())) {
throw new \moodle_exception('contextnotallowed', 'core_contentbank');
}
// If $id is defined, the file content will be replaced (instead of uploading a new one).
// Check that the user has the right permissions to replace this content file.
$id = $this->optional_param('id', null, PARAM_INT);
if ($id) {
$content = $cb->get_content_from_id($id);
$contenttype = $content->get_content_type_instance();
if (!$contenttype->can_manage($content) || !$contenttype->can_upload()) {
throw new \moodle_exception('nopermissions', 'error', '', null, get_string('replacecontent', 'contentbank'));
}
}
}
/**
* Returns form context
*
* If context depends on the form data, it is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*
* @return \context
*/
protected function get_context_for_dynamic_submission(): \context {
$contextid = $this->optional_param('contextid', null, PARAM_INT);
return \context::instance_by_id($contextid, MUST_EXIST);
}
/**
* File upload options
*
* @return array
* @throws \coding_exception
*/
protected function get_options(): array {
global $CFG;
$maxbytes = $CFG->userquota;
$maxareabytes = $CFG->userquota;
if (has_capability('moodle/user:ignoreuserquota', $this->get_context_for_dynamic_submission())) {
$maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS;
$maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED;
}
$cb = new \core_contentbank\contentbank();
$id = $this->optional_param('id', null, PARAM_INT);
if ($id) {
$content = $cb->get_content_from_id($id);
$contenttype = $content->get_content_type_instance();
$extensions = $contenttype->get_manageable_extensions();
$acceptedtypes = implode(',', $extensions);
} else {
$acceptedtypes = $cb->get_supported_extensions_as_string($this->get_context_for_dynamic_submission());
}
return ['subdirs' => 1, 'maxbytes' => $maxbytes, 'maxfiles' => -1, 'accepted_types' => $acceptedtypes,
'areamaxbytes' => $maxareabytes];
}
/**
* Process the form submission, used if form was submitted via AJAX
*
* This method can return scalar values or arrays that can be json-encoded, they will be passed to the caller JS.
*
* Submission data can be accessed as: $this->get_data()
*
* @return mixed
*/
public function process_dynamic_submission() {
global $USER;
// Get the file and create the content based on it.
$usercontext = \context_user::instance($USER->id);
$fs = get_file_storage();
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $this->get_data()->file, 'itemid, filepath,
filename', false);
if (!empty($files)) {
$file = reset($files);
$cb = new \core_contentbank\contentbank();
try {
if ($this->get_data()->id) {
$content = $cb->get_content_from_id($this->get_data()->id);
$contenttype = $content->get_content_type_instance();
$content = $contenttype->replace_content($file, $content);
} else {
$content = $cb->create_content_from_file($this->get_context_for_dynamic_submission(), $USER->id, $file);
}
$params = ['id' => $content->get_id(), 'contextid' => $this->get_context_for_dynamic_submission()->id];
$url = new \moodle_url('/contentbank/view.php', $params);
} catch (\moodle_exception $e) {
// Redirect to the right page (depending on if content is new or existing) and display an error.
if ($this->get_data()->id) {
$content = $cb->get_content_from_id($this->get_data()->id);
$params = [
'id' => $content->get_id(),
'contextid' => $this->get_context_for_dynamic_submission()->id,
'errormsg' => $e->errorcode,
];
$url = new \moodle_url('/contentbank/view.php', $params);
} else {
$url = new \moodle_url('/contentbank/index.php', [
'contextid' => $this->get_context_for_dynamic_submission()->id,
'errormsg' => $e->errorcode],
);
}
}
return ['returnurl' => $url->out(false)];
}
return null;
}
/**
* Load in existing data as form defaults
*
* Can be overridden to retrieve existing values from db by entity id and also
* to preprocess editor and filemanager elements
*
* Example:
* $this->set_data(get_entity($this->_ajaxformdata['id']));
*/
public function set_data_for_dynamic_submission(): void {
$data = (object)[
'contextid' => $this->optional_param('contextid', null, PARAM_INT),
'id' => $this->optional_param('id', null, PARAM_INT),
];
$this->set_data($data);
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* This is used in the form elements sensitive to the page url, such as Atto autosave in 'editor'
*
* If the form has arguments (such as 'id' of the element being edited), the URL should
* also have respective argument.
*
* @return \moodle_url
*/
protected function get_page_url_for_dynamic_submission(): \moodle_url {
$params = ['contextid' => $this->get_context_for_dynamic_submission()->id];
$id = $this->optional_param('id', null, PARAM_INT);
if ($id) {
$url = '/contentbank/view.php';
$params['id'] = $id;
} else {
$url = '/contentbank/index.php';
}
return new \moodle_url($url, $params);
}
}
+96
View File
@@ -0,0 +1,96 @@
<?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 helper class for the content bank.
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank;
/**
* Helper class for the content bank.
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Getting content bank page ready for the breadcrumbs.
*
* @param \context $context Context of the current page.
* @param string $title Title of the current page.
* @param bool $internal True if is an internal page, false otherwise.
*/
public static function get_page_ready(\context $context, string $title, bool $internal = false): void {
global $PAGE, $DB;
$PAGE->set_context($context);
$PAGE->set_heading(self::get_page_heading($context));
$PAGE->set_secondary_active_tab('contentbank');
$cburl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
switch ($context->contextlevel) {
case CONTEXT_COURSE:
$courseid = $context->instanceid;
$course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
$PAGE->set_course($course);
\navigation_node::override_active_url(new \moodle_url('/course/view.php', ['id' => $courseid]));
$PAGE->navbar->add($title, $cburl);
$PAGE->set_pagelayout('incourse');
break;
case CONTEXT_COURSECAT:
$coursecat = $context->instanceid;
\navigation_node::override_active_url(new \moodle_url('/course/index.php', ['categoryid' => $coursecat]));
$PAGE->navbar->add($title, $cburl);
$PAGE->set_pagelayout('coursecategory');
break;
default:
if ($node = $PAGE->navigation->find('contentbank', \global_navigation::TYPE_CUSTOM)) {
$node->make_active();
}
$PAGE->set_pagelayout('standard');
}
}
/**
* Get the page heading based on the current context
*
* @param \context $context The current context of the page
* @return string
*/
public static function get_page_heading(\context $context): string {
global $SITE;
$title = get_string('contentbank');
if ($context->id == \context_system::instance()->id) {
$title = $SITE->fullname;
} else if ($context->contextlevel == CONTEXT_COURSE) {
$course = get_course($context->instanceid);
$title = $course->fullname;
} else if ($context->contextlevel == CONTEXT_COURSECAT) {
$category = \core_course_category::get($context->instanceid);
$title = $category->get_formatted_name();
}
return $title;
}
}
+212
View File
@@ -0,0 +1,212 @@
<?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/>.
namespace core_contentbank\output;
use core\context\{course, coursecat};
use core\context_helper;
use core_contentbank\content;
use core_contentbank\contentbank;
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Class containing data for bank content
*
* @package core_contentbank
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class bankcontent implements renderable, templatable {
/**
* @var \core_contentbank\content[] Array of content bank contents.
*/
private $contents;
/**
* @var array $toolbar object.
*/
private $toolbar;
/**
* @var \context Given context. Null by default.
*/
private $context;
/**
* @var array Course categories that the user has access to.
*/
private $allowedcategories;
/**
* @var array Courses that the user has access to.
*/
private $allowedcourses;
/**
* Construct this renderable.
*
* @param \core_contentbank\content[] $contents Array of content bank contents.
* @param array $toolbar List of content bank toolbar options.
* @param \context|null $context Optional context to check (default null)
* @param contentbank $cb Contenbank object.
*/
public function __construct(array $contents, array $toolbar, ?\context $context, contentbank $cb) {
$this->contents = $contents;
$this->toolbar = $toolbar;
$this->context = $context;
list($this->allowedcategories, $this->allowedcourses) = $cb->get_contexts_with_capabilities_by_user();
}
/**
* Export the data.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
global $PAGE, $SITE;
$PAGE->requires->js_call_amd('core_contentbank/search', 'init');
$PAGE->requires->js_call_amd('core_contentbank/sort', 'init');
$data = new stdClass();
$contentdata = array();
foreach ($this->contents as $content) {
$file = $content->get_file();
$filesize = $file ? $file->get_filesize() : 0;
$mimetype = $file ? get_mimetype_description($file) : '';
$contenttypeclass = $content->get_content_type().'\\contenttype';
$contenttype = new $contenttypeclass($this->context);
if ($content->get_visibility() == content::VISIBILITY_UNLISTED) {
$name = get_string('visibilitytitleunlisted', 'contentbank', $content->get_name());
} else {
$name = $content->get_name();
}
$author = \core_user::get_user($content->get_content()->usercreated);
$contentdata[] = array(
'name' => $name,
'title' => strtolower($name),
'link' => $contenttype->get_view_url($content),
'icon' => $contenttype->get_icon($content),
'uses' => count($content->get_uses()),
'timemodified' => $content->get_timemodified(),
'bytes' => $filesize,
'size' => display_size($filesize),
'type' => $mimetype,
'author' => fullname($author),
'visibilityunlisted' => $content->get_visibility() == content::VISIBILITY_UNLISTED
);
}
$data->viewlist = get_user_preferences('core_contentbank_view_list');
$data->contents = $contentdata;
// The tools are displayed in the action bar on the index page.
foreach ($this->toolbar as $tool) {
// Customize the output of a tool, like dropdowns.
$method = 'export_tool_'.$tool['action'];
if (method_exists($this, $method)) {
$this->$method($tool);
}
$data->tools[] = $tool;
}
$allowedcontexts = [];
$systemcontext = \context_system::instance();
if (has_capability('moodle/contentbank:access', $systemcontext)) {
$allowedcontexts[$systemcontext->id] = get_string('coresystem');
}
$options = [];
foreach ($this->allowedcategories as $allowedcategory) {
context_helper::preload_from_record(clone $allowedcategory);
$options[$allowedcategory->ctxid] = format_string($allowedcategory->name, true, [
'context' => coursecat::instance($allowedcategory->ctxinstance),
]);
}
if (!empty($options)) {
$allowedcontexts['categories'] = [get_string('coursecategories') => $options];
}
$options = [];
foreach ($this->allowedcourses as $allowedcourse) {
// Don't add the frontpage course to the list.
if ($allowedcourse->id != $SITE->id) {
context_helper::preload_from_record(clone $allowedcourse);
$options[$allowedcourse->ctxid] = format_string($allowedcourse->fullname, true, [
'context' => course::instance($allowedcourse->ctxinstance),
]);
}
}
if (!empty($options)) {
$allowedcontexts['courses'] = [get_string('courses') => $options];
}
if (!empty($allowedcontexts)) {
$strchoosecontext = get_string('choosecontext', 'core_contentbank');
$singleselect = new \single_select(
new \moodle_url('/contentbank/index.php'),
'contextid',
$allowedcontexts,
$this->context->id,
$strchoosecontext
);
$singleselect->set_label($strchoosecontext, ['class' => 'sr-only']);
$data->allowedcontexts = $singleselect->export_for_template($output);
}
return $data;
}
/**
* Adds the content type items to display to the Add dropdown.
*
* Each content type is represented as an object with the properties:
* - name: the name of the content type.
* - baseurl: the base content type editor URL.
* - types: different types of the content type to display as dropdown items.
*
* @param array $tool Data for rendering the Add dropdown, including the editable content types.
*/
private function export_tool_add(array &$tool) {
$editabletypes = $tool['contenttypes'];
$addoptions = [];
foreach ($editabletypes as $class => $type) {
$contentype = new $class($this->context);
// Get the creation options of each content type.
$types = $contentype->get_contenttype_types();
if ($types) {
// Add a text describing the content type as first option. This will be displayed in the drop down to
// separate the options for the different content types.
$contentdesc = new stdClass();
$contentdesc->typename = get_string('description', $contentype->get_contenttype_name());
array_unshift($types, $contentdesc);
// Context data for the template.
$addcontenttype = new stdClass();
// Content type name.
$addcontenttype->name = $type;
// Content type editor base URL.
$tool['link']->param('plugin', $type);
$addcontenttype->baseurl = $tool['link']->out();
// Different types of the content type.
$addcontenttype->types = $types;
$addoptions[] = $addcontenttype;
}
}
$tool['contenttypes'] = $addoptions;
}
}
+204
View File
@@ -0,0 +1,204 @@
<?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/>.
namespace core_contentbank\output;
use context;
use core_contentbank\content;
use core_contentbank\contenttype;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Class containing data for the content view.
*
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class viewcontent implements renderable, templatable {
/**
* @var contenttype Content bank content type.
*/
private $contenttype;
/**
* @var stdClass Record of the contentbank_content table.
*/
private $content;
/**
* Construct this renderable.
*
* @param contenttype $contenttype Content bank content type.
* @param content $content Record of the contentbank_content table.
*/
public function __construct(contenttype $contenttype, content $content) {
$this->contenttype = $contenttype;
$this->content = $content;
}
/**
* Get the content of the "More" dropdown in the tertiary navigation
*
* @return array|null The options to be displayed in a dropdown in the tertiary navigation
* @throws \moodle_exception
*/
protected function get_edit_actions_dropdown(): ?array {
global $PAGE;
$options = [];
if ($this->contenttype->can_manage($this->content)) {
// Add the visibility item to the menu.
switch($this->content->get_visibility()) {
case content::VISIBILITY_UNLISTED:
$visibilitylabel = get_string('visibilitysetpublic', 'core_contentbank');
$newvisibility = content::VISIBILITY_PUBLIC;
break;
case content::VISIBILITY_PUBLIC:
$visibilitylabel = get_string('visibilitysetunlisted', 'core_contentbank');
$newvisibility = content::VISIBILITY_UNLISTED;
break;
default:
$url = new \moodle_url('/contentbank/index.php', ['contextid' => $this->content->get_contextid()]);
throw new moodle_exception('contentvisibilitynotfound', 'error', $url, $this->content->get_visibility());
}
if ($visibilitylabel) {
$options[$visibilitylabel] = [
'data-action' => 'setcontentvisibility',
'data-visibility' => $newvisibility,
'data-contentid' => $this->content->get_id(),
];
}
// Add the rename content item to the menu.
$options[get_string('rename')] = [
'data-action' => 'renamecontent',
'data-contentname' => $this->content->get_name(),
'data-contentid' => $this->content->get_id(),
];
if ($this->contenttype->can_upload()) {
$options[get_string('replacecontent', 'contentbank')] = ['data-action' => 'upload'];
$PAGE->requires->js_call_amd(
'core_contentbank/upload',
'initModal',
[
'[data-action=upload]',
\core_contentbank\form\upload_files::class,
$this->content->get_contextid(),
$this->content->get_id()
]
);
}
}
if ($this->contenttype->can_download($this->content)) {
$url = new moodle_url($this->contenttype->get_download_url($this->content));
$options[get_string('download')] = [
'url' => $url->out()
];
}
if ($this->contenttype->can_copy($this->content)) {
// Add the copy content item to the menu.
$options[get_string('copycontent', 'contentbank')] = [
'data-action' => 'copycontent',
'data-contentname' => get_string('copyof', 'contentbank', $this->content->get_name()),
'data-contentid' => $this->content->get_id(),
];
}
// Add the delete content item to the menu.
if ($this->contenttype->can_delete($this->content)) {
$options[get_string('delete')] = [
'data-action' => 'deletecontent',
'data-contentname' => $this->content->get_name(),
'data-uses' => count($this->content->get_uses()),
'data-contentid' => $this->content->get_id(),
'data-contextid' => $this->content->get_contextid(),
'class' => 'text-danger',
];
}
$dropdown = [];
if ($options) {
foreach ($options as $key => $attribs) {
$url = $attribs['url'] ?? '#';
$extraclasses = $attribs['class'] ?? '';
$dropdown['options'][] = [
'label' => $key,
'url' => $url,
'extraclasses' => $extraclasses,
'attributes' => array_map(function ($key, $value) {
return [
'name' => $key,
'value' => $value
];
}, array_keys($attribs), $attribs)
];
}
}
return $dropdown;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
*
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = new stdClass();
// Get the content type html.
$contenthtml = $this->contenttype->get_view_content($this->content);
$data->contenthtml = $contenthtml;
// Check if the user can edit this content type.
if ($this->contenttype->can_edit($this->content)) {
$data->usercanedit = true;
$urlparams = [
'contextid' => $this->content->get_contextid(),
'plugin' => $this->contenttype->get_plugin_name(),
'id' => $this->content->get_id()
];
$editcontenturl = new moodle_url('/contentbank/edit.php', $urlparams);
$data->editcontenturl = $editcontenturl->out(false);
}
// Close/exit link for those users who can access that context.
$context = context::instance_by_id($this->content->get_contextid());
if (has_capability('moodle/contentbank:access', $context)) {
$closeurl = new moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
$data->closeurl = $closeurl->out(false);
}
$data->actionmenu = $this->get_edit_actions_dropdown();
$data->heading = $this->content->get_name();
if ($this->content->get_visibility() == content::VISIBILITY_UNLISTED) {
$data->heading = get_string('visibilitytitleunlisted', 'contentbank', $data->heading);
}
return $data;
}
}
+303
View File
@@ -0,0 +1,303 @@
<?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/>.
/**
* Privacy provider implementation for core_contentbank.
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use core_privacy\local\request\approved_userlist;
use context_system;
use context_coursecat;
use context_course;
/**
* Privacy provider implementation for core_contentbank.
*
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('contentbank_content', [
'name' => 'privacy:metadata:content:name',
'contenttype' => 'privacy:metadata:content:contenttype',
'usercreated' => 'privacy:metadata:content:usercreated',
'usermodified' => 'privacy:metadata:content:usermodified',
'timecreated' => 'privacy:metadata:content:timecreated',
'timemodified' => 'privacy:metadata:content:timemodified',
], 'privacy:metadata:contentbankcontent');
return $collection;
}
/**
* Export all user preferences for the contentbank
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preference = get_user_preferences('core_contentbank_view_list', null, $userid);
if (isset($preference)) {
writer::export_user_preference(
'core_contentbank',
'core_contentbank_view_list',
$preference,
get_string('privacy:request:preference:set', 'core_contentbank', (object) [
'name' => 'core_contentbank_view_list',
'value' => $preference,
])
);
}
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "SELECT DISTINCT ctx.id
FROM {context} ctx
JOIN {contentbank_content} cb
ON cb.contextid = ctx.id
WHERE cb.usercreated = :userid
AND (ctx.contextlevel = :contextlevel1
OR ctx.contextlevel = :contextlevel2
OR ctx.contextlevel = :contextlevel3)";
$params = [
'userid' => $userid,
'contextlevel1' => CONTEXT_SYSTEM,
'contextlevel2' => CONTEXT_COURSECAT,
'contextlevel3' => CONTEXT_COURSE,
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
$allowedcontextlevels = [
CONTEXT_SYSTEM,
CONTEXT_COURSECAT,
CONTEXT_COURSE,
];
if (!in_array($context->contextlevel, $allowedcontextlevels)) {
return;
}
$sql = "SELECT cb.usercreated as userid
FROM {contentbank_content} cb
WHERE cb.contextid = :contextid";
$params = [
'contextid' => $context->id
];
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Remove contexts different from SYSTEM, COURSECAT or COURSE.
$contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT
|| $context->contextlevel == CONTEXT_COURSE) {
$carry[] = $context->id;
}
return $carry;
}, []);
if (empty($contextids)) {
return;
}
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
// Retrieve the contentbank_content records created for the user.
$sql = "SELECT cb.id,
cb.name,
cb.contenttype,
cb.usercreated,
cb.usermodified,
cb.timecreated,
cb.timemodified,
cb.contextid
FROM {contentbank_content} cb
WHERE cb.usercreated = :userid
AND cb.contextid {$contextsql}
ORDER BY cb.contextid";
$params = ['userid' => $userid] + $contextparams;
$contents = $DB->get_recordset_sql($sql, $params);
$data = [];
$lastcontextid = null;
$subcontext = [
get_string('name', 'core_contentbank'),
];
foreach ($contents as $content) {
// The core_contentbank data export is organised in:
// {Sytem|Course Category|Course Context Level}/Content/data.json.
if ($lastcontextid && $lastcontextid != $content->contextid) {
$context = \context::instance_by_id($lastcontextid);
writer::with_context($context)->export_data($subcontext, (object)$data);
$data = [];
}
$data[] = (object) [
'name' => $content->name,
'contenttype' => $content->contenttype,
'usercreated' => transform::user($content->usercreated),
'usermodified' => transform::user($content->usermodified),
'timecreated' => transform::datetime($content->timecreated),
'timemodified' => transform::datetime($content->timemodified)
];
$lastcontextid = $content->contextid;
// The core_contentbank files export is organised in:
// {Sytem|Course Category|Course Context Level}/Content/_files/public/_itemid/filename.
$context = \context::instance_by_id($lastcontextid);
writer::with_context($context)->export_area_files($subcontext, 'contentbank', 'public', $content->id);
}
if (!empty($data)) {
$context = \context::instance_by_id($lastcontextid);
writer::with_context($context)->export_data($subcontext, (object)$data);
}
$contents->close();
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if (!$context instanceof context_system && !$context instanceof context_coursecat
&& !$context instanceof context_course) {
return;
}
static::delete_data($context, []);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof context_system && !$context instanceof context_coursecat
&& !$context instanceof context_course) {
return;
}
static::delete_data($context, $userlist->get_userids());
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if (!$context instanceof context_system && !$context instanceof context_coursecat
&& !$context instanceof context_course) {
continue;
}
static::delete_data($context, [$userid]);
}
}
/**
* Delete data related to a context and users (if defined).
*
* @param context $context A context.
* @param array $userids The user IDs.
*/
protected static function delete_data(\context $context, array $userids) {
global $DB;
$params = ['contextid' => $context->id];
$select = 'contextid = :contextid';
// Delete the Content Bank files.
if (!empty($userids)) {
list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params += $inparams;
$select .= ' AND usercreated '.$insql;
}
$fs = get_file_storage();
$contents = $DB->get_records_select('contentbank_content',
$select, $params);
foreach ($contents as $content) {
$fs->delete_area_files($content->contextid, 'contentbank', 'public', $content->id);
}
// Delete all the contents.
$DB->delete_records_select('contentbank_content', $select, $params);
}
}