Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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  namespace core_contentbank;
  18  
  19  use core\event\contentbank_content_created;
  20  use core\event\contentbank_content_deleted;
  21  use core\event\contentbank_content_viewed;
  22  use stored_file;
  23  use moodle_url;
  24  
  25  /**
  26   * Content type manager class
  27   *
  28   * @package    core_contentbank
  29   * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
  30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  abstract class contenttype {
  33  
  34      /** @var string Constant representing whether the plugin implements uploading feature */
  35      const CAN_UPLOAD = 'upload';
  36  
  37      /** @var string Constant representing whether the plugin implements edition feature */
  38      const CAN_EDIT = 'edit';
  39  
  40      /**
  41       * @var string Constant representing whether the plugin implements download feature
  42       * @since  Moodle 3.10
  43       */
  44      const CAN_DOWNLOAD = 'download';
  45  
  46      /**
  47       * @var string Constant representing whether the plugin implements copy feature
  48       * @since  Moodle 4.3
  49       */
  50      const CAN_COPY = 'copy';
  51  
  52      /** @var \context This contenttype's context. **/
  53      protected $context = null;
  54  
  55      /**
  56       * Content type constructor
  57       *
  58       * @param \context $context Optional context to check (default null)
  59       */
  60      public function __construct(\context $context = null) {
  61          if (empty($context)) {
  62              $context = \context_system::instance();
  63          }
  64          $this->context = $context;
  65      }
  66  
  67      /**
  68       * Fills content_bank table with appropiate information.
  69       *
  70       * @throws dml_exception A DML specific exception is thrown for any creation error.
  71       * @param \stdClass $record An optional content record compatible object (default null)
  72       * @return content  Object with content bank information.
  73       */
  74      public function create_content(\stdClass $record = null): content {
  75          global $USER, $DB, $CFG;
  76  
  77          $entry = new \stdClass();
  78          if (isset($record->visibility)) {
  79              $entry->visibility = $record->visibility;
  80          } else {
  81              $usercreated = $record->usercreated ?? $USER->id;
  82              $entry->visibility = get_user_preferences('core_contentbank_visibility',
  83                  $CFG->defaultpreference_core_contentbank_visibility, $usercreated);
  84          }
  85          $entry->contenttype = $this->get_contenttype_name();
  86          $entry->contextid = $this->context->id;
  87          $entry->name = $record->name ?? '';
  88          $entry->usercreated = $record->usercreated ?? $USER->id;
  89          $entry->timecreated = time();
  90          $entry->usermodified = $entry->usercreated;
  91          $entry->timemodified = $entry->timecreated;
  92          $entry->configdata = $record->configdata ?? '';
  93          $entry->instanceid = $record->instanceid ?? 0;
  94          $entry->id = $DB->insert_record('contentbank_content', $entry);
  95          $classname = '\\'.$entry->contenttype.'\\content';
  96          $content = new $classname($entry);
  97          // Trigger an event for creating the content.
  98          $event = contentbank_content_created::create_from_record($content->get_content());
  99          $event->trigger();
 100          return $content;
 101      }
 102  
 103      /**
 104       * Create a new content from an uploaded file.
 105       *
 106       * @throws file_exception If file operations fail
 107       * @throws dml_exception if the content creation fails
 108       * @param stored_file $file the uploaded file
 109       * @param \stdClass|null $record an optional content record
 110       * @return content  Object with content bank information.
 111       */
 112      public function upload_content(stored_file $file, \stdClass $record = null): content {
 113          if (empty($record)) {
 114              $record = new \stdClass();
 115              $record->name = $file->get_filename();
 116          }
 117          $content = $this->create_content($record);
 118          try {
 119              $content->import_file($file);
 120          } catch (\moodle_exception $e) {
 121              $this->delete_content($content);
 122              throw new \moodle_exception($e->errorcode);
 123          }
 124  
 125          return $content;
 126      }
 127  
 128      /**
 129       * Replace a content using an uploaded file.
 130       *
 131       * @throws file_exception If file operations fail
 132       * @throws dml_exception if the content creation fails
 133       * @param stored_file $file the uploaded file
 134       * @param content $content the original content record
 135       * @return content Object with the updated content bank information.
 136       */
 137      public function replace_content(stored_file $file, content $content): content {
 138          $content->import_file($file);
 139          $content->update_content();
 140          return $content;
 141      }
 142  
 143      /**
 144       * Delete this content from the content_bank.
 145       * This method can be overwritten by the plugins if they need to delete specific information.
 146       *
 147       * @param  content $content The content to delete.
 148       * @return boolean true if the content has been deleted; false otherwise.
 149       */
 150      public function delete_content(content $content): bool {
 151          global $DB;
 152  
 153          // Delete the file if it exists.
 154          if ($file = $content->get_file()) {
 155              $file->delete();
 156          }
 157  
 158          // Delete the contentbank DB entry.
 159          $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
 160          if ($result) {
 161              // Trigger an event for deleting this content.
 162              $record = $content->get_content();
 163              $event = contentbank_content_deleted::create([
 164                  'objectid' => $content->get_id(),
 165                  'relateduserid' => $record->usercreated,
 166                  'context' => \context::instance_by_id($record->contextid),
 167                  'other' => [
 168                      'contenttype' => $content->get_content_type(),
 169                      'name' => $content->get_name()
 170                  ]
 171              ]);
 172              $event->add_record_snapshot('contentbank_content', $record);
 173              $event->trigger();
 174          }
 175          return $result;
 176      }
 177  
 178      /**
 179       * Rename this content from the content_bank.
 180       * This method can be overwritten by the plugins if they need to change some other specific information.
 181       *
 182       * @param  content $content The content to rename.
 183       * @param  string $name  The name of the content.
 184       * @return boolean true if the content has been renamed; false otherwise.
 185       */
 186      public function rename_content(content $content, string $name): bool {
 187          return $content->set_name($name);
 188      }
 189  
 190      /**
 191       * Move content to another context.
 192       * This method can be overwritten by the plugins if they need to change some other specific information.
 193       *
 194       * @param  content $content The content to rename.
 195       * @param  \context $context  The new context.
 196       * @return boolean true if the content has been renamed; false otherwise.
 197       */
 198      public function move_content(content $content, \context $context): bool {
 199          return $content->set_contextid($context->id);
 200      }
 201  
 202      /**
 203       * Returns the contenttype name of this content.
 204       *
 205       * @return string   Content type of the current instance
 206       */
 207      public function get_contenttype_name(): string {
 208          $classname = get_class($this);
 209          $contenttype = explode('\\', $classname);
 210          return array_shift($contenttype);
 211      }
 212  
 213      /**
 214       * Returns the plugin name of the current instance.
 215       *
 216       * @return string   Plugin name of the current instance
 217       */
 218      public function get_plugin_name(): string {
 219          $contenttype = $this->get_contenttype_name();
 220          $plugin = explode('_', $contenttype);
 221          return array_pop($plugin);
 222      }
 223  
 224      /**
 225       * Returns the URL where the content will be visualized.
 226       *
 227       * @param  content $content The content to be displayed.
 228       * @return string           URL where to visualize the given content.
 229       */
 230      public function get_view_url(content $content): string {
 231          return new moodle_url('/contentbank/view.php', ['id' => $content->get_id()]);
 232      }
 233  
 234      /**
 235       * Returns the HTML content to add to view.php visualizer.
 236       *
 237       * @param  content $content The content to be displayed.
 238       * @return string           HTML code to include in view.php.
 239       */
 240      public function get_view_content(content $content): string {
 241          // Trigger an event for viewing this content.
 242          $event = contentbank_content_viewed::create_from_record($content->get_content());
 243          $event->trigger();
 244  
 245          return '';
 246      }
 247  
 248      /**
 249       * Returns the URL to download the content.
 250       *
 251       * @since  Moodle 3.10
 252       * @param  content $content The content to be downloaded.
 253       * @return string           URL with the content to download.
 254       */
 255      public function get_download_url(content $content): string {
 256          $downloadurl = '';
 257          $file = $content->get_file();
 258          if (!empty($file)) {
 259              $url = \moodle_url::make_pluginfile_url(
 260                  $file->get_contextid(),
 261                  $file->get_component(),
 262                  $file->get_filearea(),
 263                  $file->get_itemid(),
 264                  $file->get_filepath(),
 265                  $file->get_filename()
 266              );
 267              $downloadurl = $url->out(false);
 268          }
 269  
 270          return $downloadurl;
 271      }
 272  
 273      /**
 274       * Returns the HTML code to render the icon for content bank contents.
 275       *
 276       * @param  content $content The content to be displayed.
 277       * @return string               HTML code to render the icon
 278       */
 279      public function get_icon(content $content): string {
 280          global $OUTPUT;
 281          return $OUTPUT->image_url('f/unknown')->out(false);
 282      }
 283  
 284      /**
 285       * Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
 286       *
 287       * @return bool     True if content could be accessed. False otherwise.
 288       */
 289      final public function can_access(): bool {
 290          $classname = 'contenttype/'.$this->get_plugin_name();
 291          $capability = $classname.":access";
 292          $hascapabilities = has_capability('moodle/contentbank:access', $this->context)
 293              && has_capability($capability, $this->context);
 294          return $hascapabilities && $this->is_access_allowed();
 295      }
 296  
 297      /**
 298       * Returns user has access capability for the content itself.
 299       *
 300       * @return bool     True if content could be accessed. False otherwise.
 301       */
 302      protected function is_access_allowed(): bool {
 303          // Plugins can overwrite this function to add any check they need.
 304          return true;
 305      }
 306  
 307      /**
 308       * Returns the user has permission to upload new content.
 309       *
 310       * @return bool     True if content could be uploaded. False otherwise.
 311       */
 312      final public function can_upload(): bool {
 313          if (!$this->is_feature_supported(self::CAN_UPLOAD)) {
 314              return false;
 315          }
 316          if (!$this->can_access()) {
 317              return false;
 318          }
 319  
 320          $classname = 'contenttype/'.$this->get_plugin_name();
 321          $uploadcap = $classname.':upload';
 322          $hascapabilities = has_capability('moodle/contentbank:upload', $this->context)
 323              && has_capability($uploadcap, $this->context);
 324          return $hascapabilities && $this->is_upload_allowed();
 325      }
 326  
 327      /**
 328       * Returns plugin allows uploading.
 329       *
 330       * @return bool     True if plugin allows uploading. False otherwise.
 331       */
 332      protected function is_upload_allowed(): bool {
 333          // Plugins can overwrite this function to add any check they need.
 334          return true;
 335      }
 336  
 337      /**
 338       * Check if the user can delete this content.
 339       *
 340       * @param  content $content The content to be deleted.
 341       * @return bool True if content could be uploaded. False otherwise.
 342       */
 343      final public function can_delete(content $content): bool {
 344          global $USER;
 345  
 346          if ($this->context->id != $content->get_content()->contextid) {
 347              // The content has to have exactly the same context as this contenttype.
 348              return false;
 349          }
 350  
 351          $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
 352          if ($content->get_content()->usercreated == $USER->id) {
 353              // This content has been created by the current user; check if she can delete her content.
 354              $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
 355          }
 356  
 357          return $hascapability && $this->is_delete_allowed($content);
 358      }
 359  
 360      /**
 361       * Returns if content allows deleting.
 362       *
 363       * @param  content $content The content to be deleted.
 364       * @return bool True if content allows uploading. False otherwise.
 365       */
 366      protected function is_delete_allowed(content $content): bool {
 367          // Plugins can overwrite this function to add any check they need.
 368          return true;
 369      }
 370  
 371      /**
 372       * Check if the user can managed this content.
 373       *
 374       * @param  content $content The content to be managed.
 375       * @return bool     True if content could be managed. False otherwise.
 376       */
 377      public final function can_manage(content $content): bool {
 378          global $USER;
 379  
 380          if ($this->context->id != $content->get_content()->contextid) {
 381              // The content has to have exactly the same context as this contenttype.
 382              return false;
 383          }
 384  
 385          // Check main contentbank management permission.
 386          $hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
 387          if ($content->get_content()->usercreated == $USER->id) {
 388              // This content has been created by the current user; check if they can manage their content.
 389              $hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
 390          }
 391  
 392          return $hascapability && $this->is_manage_allowed($content);
 393      }
 394  
 395      /**
 396       * Returns if content allows managing.
 397       *
 398       * @param  content $content The content to be managed.
 399       * @return bool True if content allows uploading. False otherwise.
 400       */
 401      protected function is_manage_allowed(content $content): bool {
 402          // Plugins can overwrite this function to add any check they need.
 403          return true;
 404      }
 405  
 406      /**
 407       * Returns whether or not the user has permission to use the editor.
 408       * This function will be called with the content to be edited as parameter,
 409       * or null when is checking permission to create a new content using the editor.
 410       *
 411       * @param  content $content The content to be edited or null when creating a new content.
 412       * @return bool     True if the user can edit content. False otherwise.
 413       */
 414      final public function can_edit(?content $content = null): bool {
 415          if (!$this->is_feature_supported(self::CAN_EDIT)) {
 416              return false;
 417          }
 418  
 419          if (!$this->can_access()) {
 420              return false;
 421          }
 422  
 423          if (!is_null($content) && !$this->can_manage($content)) {
 424              return false;
 425          }
 426  
 427          $classname = 'contenttype/'.$this->get_plugin_name();
 428  
 429          $editioncap = $classname.':useeditor';
 430          $hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
 431          return $hascapabilities && $this->is_edit_allowed($content);
 432      }
 433  
 434      /**
 435       * Returns plugin allows edition.
 436       *
 437       * @param  content $content The content to be edited.
 438       * @return bool     True if plugin allows edition. False otherwise.
 439       */
 440      protected function is_edit_allowed(?content $content): bool {
 441          // Plugins can overwrite this function to add any check they need.
 442          return true;
 443      }
 444  
 445      /**
 446       * Returns whether or not the user has permission to download the content.
 447       *
 448       * @since  Moodle 3.10
 449       * @param  content $content The content to be downloaded.
 450       * @return bool    True if the user can download the content. False otherwise.
 451       */
 452      final public function can_download(content $content): bool {
 453          if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
 454              return false;
 455          }
 456  
 457          if (!$this->can_access()) {
 458              return false;
 459          }
 460  
 461          $hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
 462          return $hascapability && $this->is_download_allowed($content);
 463      }
 464  
 465      /**
 466       * Returns whether or not the user has permission to copy the content.
 467       *
 468       * @since  Moodle 4.3
 469       * @param  content $content The content to be copied.
 470       * @return bool    True if the user can copy the content. False otherwise.
 471       */
 472      final public function can_copy(content $content): bool {
 473          global $USER;
 474  
 475          if (!$this->is_feature_supported(self::CAN_COPY)) {
 476              return false;
 477          }
 478  
 479          if (!$this->can_access()) {
 480              return false;
 481          }
 482  
 483          if (!$this->is_copy_allowed($content)) {
 484              return false;
 485          }
 486  
 487          $hascapability = has_capability('moodle/contentbank:copyanycontent', $this->context);
 488          if (!$hascapability && ($content->get_content()->usercreated == $USER->id)) {
 489              $hascapability = has_capability('moodle/contentbank:copycontent', $this->context);
 490          }
 491          return $hascapability;
 492      }
 493  
 494      /**
 495       * Returns plugin allows downloading.
 496       *
 497       * @since  Moodle 3.10
 498       * @param  content $content The content to be downloaed.
 499       * @return bool    True if plugin allows downloading. False otherwise.
 500       */
 501      protected function is_download_allowed(content $content): bool {
 502          // Plugins can overwrite this function to add any check they need.
 503          return true;
 504      }
 505  
 506      /**
 507       * Returns plugin allows copying.
 508       *
 509       * @since  Moodle 4.3
 510       * @param  content $content The content to be copied.
 511       * @return bool    True if plugin allows copying. False otherwise.
 512       */
 513      protected function is_copy_allowed(content $content): bool {
 514          // Plugins can overwrite this function to add any check they need.
 515          return true;
 516      }
 517  
 518      /**
 519       * Returns the plugin supports the feature.
 520       *
 521       * @param string $feature Feature code e.g CAN_UPLOAD
 522       * @return bool     True if content could be uploaded. False otherwise.
 523       */
 524      final public function is_feature_supported(string $feature): bool {
 525          return in_array($feature, $this->get_implemented_features());
 526      }
 527  
 528      /**
 529       * Return an array of implemented features by the plugins.
 530       *
 531       * @return array
 532       */
 533      abstract protected function get_implemented_features(): array;
 534  
 535      /**
 536       * Return an array of extensions the plugins could manage.
 537       *
 538       * @return array
 539       */
 540      abstract public function get_manageable_extensions(): array;
 541  
 542      /**
 543       * Returns the list of different types of the given content type.
 544       *
 545       * A content type can have one or more options for creating content. This method will report all of them or only the content
 546       * type itself if it has no other options.
 547       *
 548       * @return array An object for each type:
 549       *     - string typename: descriptive name of the type.
 550       *     - string typeeditorparams: params required by this content type editor.
 551       *     - url typeicon: this type icon.
 552       */
 553      abstract public function get_contenttype_types(): array;
 554  }