Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Content manager class
  19   *
  20   * @package    core_contentbank
  21   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_contentbank;
  26  
  27  use core_text;
  28  use stored_file;
  29  use stdClass;
  30  use coding_exception;
  31  use context;
  32  use moodle_url;
  33  use core\event\contentbank_content_updated;
  34  
  35  /**
  36   * Content manager class
  37   *
  38   * @package    core_contentbank
  39   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  abstract class content {
  43      /**
  44       * @var int Visibility value. Public content is visible to all users with access to the content bank of the
  45       * appropriate context.
  46       */
  47      public const VISIBILITY_PUBLIC = 1;
  48  
  49      /**
  50       * @var int Visibility value. Unlisted content is only visible to the author and to users with
  51       * moodle/contentbank:viewunlistedcontent capability.
  52       */
  53      public const VISIBILITY_UNLISTED = 2;
  54  
  55      /** @var stdClass $content The content of the current instance. **/
  56      protected $content  = null;
  57  
  58      /**
  59       * Content bank constructor
  60       *
  61       * @param stdClass $record A contentbank_content record.
  62       * @throws coding_exception If content type is not right.
  63       */
  64      public function __construct(stdClass $record) {
  65          // Content type should exist and be linked to plugin classname.
  66          $classname = $record->contenttype.'\\content';
  67          if (get_class($this) != $classname) {
  68              throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
  69          }
  70          $typeclass = $record->contenttype.'\\contenttype';
  71          if (!class_exists($typeclass)) {
  72              throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
  73          }
  74          // A record with the id must exist in 'contentbank_content' table.
  75          // To improve performance, we are only checking the id is set, but no querying the database.
  76          if (!isset($record->id)) {
  77              throw new coding_exception(get_string('invalidcontentid', 'error'));
  78          }
  79          $this->content = $record;
  80      }
  81  
  82      /**
  83       * Returns $this->content.
  84       *
  85       * @return stdClass  $this->content.
  86       */
  87      public function get_content(): stdClass {
  88          return $this->content;
  89      }
  90  
  91      /**
  92       * Returns $this->content->contenttype.
  93       *
  94       * @return string  $this->content->contenttype.
  95       */
  96      public function get_content_type(): string {
  97          return $this->content->contenttype;
  98      }
  99  
 100      /**
 101       * Return the contenttype instance of this content.
 102       *
 103       * @return contenttype The content type instance
 104       */
 105      public function get_content_type_instance(): contenttype {
 106          $context = context::instance_by_id($this->content->contextid);
 107          $contenttypeclass = "\\{$this->content->contenttype}\\contenttype";
 108          return new $contenttypeclass($context);
 109      }
 110  
 111      /**
 112       * Returns $this->content->timemodified.
 113       *
 114       * @return int  $this->content->timemodified.
 115       */
 116      public function get_timemodified(): int {
 117          return $this->content->timemodified;
 118      }
 119  
 120      /**
 121       * Updates content_bank table with information in $this->content.
 122       *
 123       * @return boolean  True if the content has been succesfully updated. False otherwise.
 124       * @throws \coding_exception if not loaded.
 125       */
 126      public function update_content(): bool {
 127          global $USER, $DB;
 128  
 129          // A record with the id must exist in 'contentbank_content' table.
 130          // To improve performance, we are only checking the id is set, but no querying the database.
 131          if (!isset($this->content->id)) {
 132              throw new coding_exception(get_string('invalidcontentid', 'error'));
 133          }
 134          $this->content->usermodified = $USER->id;
 135          $this->content->timemodified = time();
 136          $result = $DB->update_record('contentbank_content', $this->content);
 137          if ($result) {
 138              // Trigger an event for updating this content.
 139              $event = contentbank_content_updated::create_from_record($this->content);
 140              $event->trigger();
 141          }
 142          return $result;
 143      }
 144  
 145      /**
 146       * Set a new name to the content.
 147       *
 148       * @param string $name  The name of the content.
 149       * @return bool  True if the content has been succesfully updated. False otherwise.
 150       * @throws \coding_exception if not loaded.
 151       */
 152      public function set_name(string $name): bool {
 153          $name = trim($name);
 154          if ($name === '') {
 155              return false;
 156          }
 157  
 158          // Clean name.
 159          $name = clean_param($name, PARAM_TEXT);
 160          if (core_text::strlen($name) > 255) {
 161              $name = core_text::substr($name, 0, 255);
 162          }
 163  
 164          $oldname = $this->content->name;
 165          $this->content->name = $name;
 166          $updated = $this->update_content();
 167          if (!$updated) {
 168              $this->content->name = $oldname;
 169          }
 170          return $updated;
 171      }
 172  
 173      /**
 174       * Returns the name of the content.
 175       *
 176       * @return string   The name of the content.
 177       */
 178      public function get_name(): string {
 179          return $this->content->name;
 180      }
 181  
 182      /**
 183       * Set a new contextid to the content.
 184       *
 185       * @param int $contextid  The new contextid of the content.
 186       * @return bool  True if the content has been succesfully updated. False otherwise.
 187       */
 188      public function set_contextid(int $contextid): bool {
 189          if ($this->content->contextid == $contextid) {
 190              return true;
 191          }
 192  
 193          $oldcontextid = $this->content->contextid;
 194          $this->content->contextid = $contextid;
 195          $updated = $this->update_content();
 196          if ($updated) {
 197              // Move files to new context
 198              $fs = get_file_storage();
 199              $fs->move_area_files_to_new_context($oldcontextid, $contextid, 'contentbank', 'public', $this->content->id);
 200          } else {
 201              $this->content->contextid = $oldcontextid;
 202          }
 203          return $updated;
 204      }
 205  
 206      /**
 207       * Returns the contextid of the content.
 208       *
 209       * @return int   The id of the content context.
 210       */
 211      public function get_contextid(): string {
 212          return $this->content->contextid;
 213      }
 214  
 215      /**
 216       * Returns the content ID.
 217       *
 218       * @return int   The content ID.
 219       */
 220      public function get_id(): int {
 221          return $this->content->id;
 222      }
 223  
 224      /**
 225       * Change the content instanceid value.
 226       *
 227       * @param int $instanceid    New instanceid for this content
 228       * @return boolean           True if the instanceid has been succesfully updated. False otherwise.
 229       */
 230      public function set_instanceid(int $instanceid): bool {
 231          $this->content->instanceid = $instanceid;
 232          return $this->update_content();
 233      }
 234  
 235      /**
 236       * Returns the $instanceid of this content.
 237       *
 238       * @return int   contentbank instanceid
 239       */
 240      public function get_instanceid(): int {
 241          return $this->content->instanceid;
 242      }
 243  
 244      /**
 245       * Change the content config values.
 246       *
 247       * @param string $configdata    New config information for this content
 248       * @return boolean              True if the configdata has been succesfully updated. False otherwise.
 249       */
 250      public function set_configdata(string $configdata): bool {
 251          $this->content->configdata = $configdata;
 252          return $this->update_content();
 253      }
 254  
 255      /**
 256       * Return the content config values.
 257       *
 258       * @return mixed   Config information for this content (json decoded)
 259       */
 260      public function get_configdata() {
 261          return $this->content->configdata;
 262      }
 263  
 264      /**
 265       * Sets a new content visibility and saves it to database.
 266       *
 267       * @param int $visibility Must be self::PUBLIC or self::UNLISTED
 268       * @return bool
 269       * @throws coding_exception
 270       */
 271      public function set_visibility(int $visibility): bool {
 272          if (!in_array($visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED])) {
 273              return false;
 274          }
 275          $this->content->visibility = $visibility;
 276          return $this->update_content();
 277      }
 278  
 279      /**
 280       * Return true if the content may be shown to other users in the content bank.
 281       *
 282       * @return boolean
 283       */
 284      public function get_visibility(): int {
 285          return $this->content->visibility;
 286      }
 287  
 288      /**
 289       * Import a file as a valid content.
 290       *
 291       * By default, all content has a public file area to interact with the content bank
 292       * repository. This method should be overridden by contentypes which does not simply
 293       * upload to the public file area.
 294       *
 295       * If any, the method will return the final stored_file. This way it can be invoked
 296       * as parent::import_file in case any plugin want to store the file in the public area
 297       * and also parse it.
 298       *
 299       * @throws file_exception If file operations fail
 300       * @param stored_file $file File to store in the content file area.
 301       * @return stored_file|null the stored content file or null if the file is discarted.
 302       */
 303      public function import_file(stored_file $file): ?stored_file {
 304          $originalfile = $this->get_file();
 305          if ($originalfile) {
 306              $originalfile->replace_file_with($file);
 307              return $originalfile;
 308          } else {
 309              $itemid = $this->get_id();
 310              $fs = get_file_storage();
 311              $filerecord = [
 312                  'contextid' => $this->get_contextid(),
 313                  'component' => 'contentbank',
 314                  'filearea' => 'public',
 315                  'itemid' => $this->get_id(),
 316                  'filepath' => '/',
 317                  'filename' => $file->get_filename(),
 318                  'timecreated' => time(),
 319              ];
 320              return $fs->create_file_from_storedfile($filerecord, $file);
 321          }
 322      }
 323  
 324      /**
 325       * Returns the $file related to this content.
 326       *
 327       * @return stored_file  File stored in content bank area related to the given itemid.
 328       * @throws \coding_exception if not loaded.
 329       */
 330      public function get_file(): ?stored_file {
 331          $itemid = $this->get_id();
 332          $fs = get_file_storage();
 333          $files = $fs->get_area_files(
 334              $this->content->contextid,
 335              'contentbank',
 336              'public',
 337              $itemid,
 338              'itemid, filepath, filename',
 339              false
 340          );
 341          if (!empty($files)) {
 342              $file = reset($files);
 343              return $file;
 344          }
 345          return null;
 346      }
 347  
 348      /**
 349       * Returns the places where the file associated to this content is used or an empty array if the content has no file.
 350       *
 351       * @return array of stored_file where current file content is used or empty array if it hasn't any file.
 352       * @since 3.11
 353       */
 354      public function get_uses(): ?array {
 355          $references = [];
 356  
 357          $file = $this->get_file();
 358          if ($file != null) {
 359              $fs = get_file_storage();
 360              $references = $fs->get_references_by_storedfile($file);
 361          }
 362  
 363          return $references;
 364      }
 365  
 366      /**
 367       * Returns the file url related to this content.
 368       *
 369       * @return string       URL of the file stored in content bank area related to the given itemid.
 370       * @throws \coding_exception if not loaded.
 371       */
 372      public function get_file_url(): string {
 373          if (!$file = $this->get_file()) {
 374              return '';
 375          }
 376          $fileurl = moodle_url::make_pluginfile_url(
 377              $this->content->contextid,
 378              'contentbank',
 379              'public',
 380              $file->get_itemid(),
 381              $file->get_filepath(),
 382              $file->get_filename()
 383          );
 384  
 385          return $fileurl;
 386      }
 387  
 388      /**
 389       * Returns user has access permission for the content itself (based on what plugin needs).
 390       *
 391       * @return bool     True if content could be accessed. False otherwise.
 392       */
 393      public function is_view_allowed(): bool {
 394          // Plugins can overwrite this method in case they want to check something related to content properties.
 395          global $USER;
 396          $context = \context::instance_by_id($this->get_contextid());
 397  
 398          return $USER->id == $this->content->usercreated ||
 399              $this->get_visibility() == self::VISIBILITY_PUBLIC ||
 400              has_capability('moodle/contentbank:viewunlistedcontent', $context);
 401      }
 402  }