Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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