<?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 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
*/
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 Exception;
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 {
< /** Plugin implements uploading feature */
> /** @var string Constant representing whether the plugin implements uploading feature */
const CAN_UPLOAD = 'upload';
< /** Plugin implements edition feature */
> /** @var string Constant representing whether the plugin implements edition feature */
const CAN_EDIT = 'edit';
> /**
/** @var \context This contenttype's context. **/
> * @var string Constant representing whether the plugin implements download feature
protected $context = null;
> * @since Moodle 3.10
> */
/**
> const CAN_DOWNLOAD = 'download';
* 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;
> global $USER, $DB, $CFG;
$entry = new \stdClass();
> if (isset($record->visibility)) {
$entry->contenttype = $this->get_contenttype_name();
> $entry->visibility = $record->visibility;
$entry->contextid = $this->context->id;
> } else {
$entry->name = $record->name ?? '';
> $usercreated = $record->usercreated ?? $USER->id;
$entry->usercreated = $record->usercreated ?? $USER->id;
> $entry->visibility = get_user_preferences('core_contentbank_visibility',
$entry->timecreated = time();
> $CFG->defaultpreference_core_contentbank_visibility, $usercreated);
$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 (Exception $e) {
$this->delete_content($content);
throw $e;
}
return $content;
}
/**
> * Replace a content using an uploaded file.
* Delete this content from the content_bank.
> *
* This method can be overwritten by the plugins if they need to delete specific information.
> * @throws file_exception If file operations fail
*
> * @throws dml_exception if the content creation fails
* @param content $content The content to delete.
> * @param stored_file $file the uploaded file
* @return boolean true if the content has been deleted; false otherwise.
> * @param content $content the original content record
*/
> * @return content Object with the updated content bank information.
public function delete_content(content $content): bool {
> */
global $DB;
> public function replace_content(stored_file $file, content $content): content {
> $content->import_file($file);
// Delete the file if it exists.
> $content->update_content();
if ($file = $content->get_file()) {
> return $content;
$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.
* Returns the HTML code to render the icon for content bank contents.
> *
*
> * @since Moodle 3.10
* @param content $content The content to be displayed.
> * @param content $content The content to be downloaded.
* @return string HTML code to render the icon
> * @return string URL with the content to download.
*/
> */
public function get_icon(content $content): string {
> public function get_download_url(content $content): string {
global $OUTPUT;
> $downloadurl = '';
return $OUTPUT->image_url('f/unknown-64', 'moodle')->out(false);
> $file = $content->get_file();
}
> if (!empty($file)) {
> $url = \moodle_url::make_pluginfile_url(
/**
> $file->get_contextid(),
* Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
> $file->get_component(),
*
> $file->get_filearea(),
* @return bool True if content could be accessed. False otherwise.
> $file->get_itemid(),
*/
> $file->get_filepath(),
final public function can_access(): bool {
> $file->get_filename()
$classname = 'contenttype/'.$this->get_plugin_name();
> );
$capability = $classname.":access";
> $downloadurl = $url->out(false);
$hascapabilities = has_capability('moodle/contentbank:access', $this->context)
> }
&& has_capability($capability, $this->context);
>
return $hascapabilities && $this->is_access_allowed();
> return $downloadurl;
}
> }
>
/**
> /**
* 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.
*/
public final 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.
// Plugins can overwrite this function to add any check they need.
> return true;
return true;
> }
}
>
> /**
/**
> * Returns whether or not the user has permission to download the content.
* Returns the plugin supports the feature.
> *
*
> * @since Moodle 3.10
* @param string $feature Feature code e.g CAN_UPLOAD
> * @param content $content The content to be downloaded.
* @return bool True if content could be uploaded. False otherwise.
> * @return bool True if the user can download the content. False otherwise.
*/
> */
final public function is_feature_supported(string $feature): bool {
> final public function can_download(content $content): bool {
return in_array($feature, $this->get_implemented_features());
> if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
}
> return false;
> }
/**
>
* Return an array of implemented features by the plugins.
> if (!$this->can_access()) {
*
> return false;
* @return array
> }
*/
>
abstract protected function get_implemented_features(): array;
> $hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
> return $hascapability && $this->is_download_allowed($content);
/**
> }
* Return an array of extensions the plugins could manage.
>
*
> /**
* @return array
> * Returns plugin allows downloading.
*/
> *
abstract public function get_manageable_extensions(): array;
> * @since Moodle 3.10
> * @param content $content The content to be downloaed.
/**
> * @return bool True if plugin allows downloading. False otherwise.
* Returns the list of different types of the given content type.
> */
*
> protected function is_download_allowed(content $content): bool {
* 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;
}