See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body