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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body