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