Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?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.
     *
< * @throws file_exception If file operations fail
* @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 {
< $itemid = $this->get_id();
$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); } }