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