See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 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 bank 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_plugin_manager; 28 use stored_file; 29 use context; 30 31 /** 32 * Content bank class 33 * 34 * @package core_contentbank 35 * @copyright 2020 Amaia Anabitarte <amaia@moodle.com> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class contentbank { 39 40 /** @var array All the context levels allowed in the content bank */ 41 private const ALLOWED_CONTEXT_LEVELS = [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE]; 42 43 /** @var array Enabled content types. */ 44 private $enabledcontenttypes = null; 45 46 /** 47 * Obtains the list of core_contentbank_content objects currently active. 48 * 49 * The list does not include players which are disabled. 50 * 51 * @return string[] Array of contentbank contenttypes. 52 */ 53 public function get_enabled_content_types(): array { 54 if (!is_null($this->enabledcontenttypes)) { 55 return $this->enabledcontenttypes; 56 } 57 58 $enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins(); 59 $types = []; 60 foreach ($enabledtypes as $name) { 61 $contenttypeclassname = "\\contenttype_$name\\contenttype"; 62 $contentclassname = "\\contenttype_$name\\content"; 63 if (class_exists($contenttypeclassname) && class_exists($contentclassname)) { 64 $types[$contenttypeclassname] = $name; 65 } 66 } 67 return $this->enabledcontenttypes = $types; 68 } 69 70 /** 71 * Obtains an array of supported extensions by active plugins. 72 * 73 * @return array The array with all the extensions supported and the supporting plugin names. 74 */ 75 public function load_all_supported_extensions(): array { 76 $extensionscache = \cache::make('core', 'contentbank_enabled_extensions'); 77 $supportedextensions = $extensionscache->get('enabled_extensions'); 78 if ($supportedextensions === false) { 79 // Load all enabled extensions. 80 $supportedextensions = []; 81 foreach ($this->get_enabled_content_types() as $type) { 82 $classname = "\\contenttype_$type\\contenttype"; 83 $contenttype = new $classname; 84 if ($contenttype->is_feature_supported($contenttype::CAN_UPLOAD)) { 85 $extensions = $contenttype->get_manageable_extensions(); 86 foreach ($extensions as $extension) { 87 if (array_key_exists($extension, $supportedextensions)) { 88 $supportedextensions[$extension][] = $type; 89 } else { 90 $supportedextensions[$extension] = [$type]; 91 } 92 } 93 } 94 } 95 $extensionscache->set('enabled_extensions', $supportedextensions); 96 } 97 return $supportedextensions; 98 } 99 100 /** 101 * Obtains an array of supported extensions in the given context. 102 * 103 * @param context $context Optional context to check (default null) 104 * @return array The array with all the extensions supported and the supporting plugin names. 105 */ 106 public function load_context_supported_extensions(context $context = null): array { 107 $extensionscache = \cache::make('core', 'contentbank_context_extensions'); 108 109 $contextextensions = $extensionscache->get($context->id); 110 if ($contextextensions === false) { 111 $contextextensions = []; 112 $supportedextensions = $this->load_all_supported_extensions(); 113 foreach ($supportedextensions as $extension => $types) { 114 foreach ($types as $type) { 115 $classname = "\\contenttype_$type\\contenttype"; 116 $contenttype = new $classname($context); 117 if ($contenttype->can_upload()) { 118 $contextextensions[$extension] = $type; 119 break; 120 } 121 } 122 } 123 $extensionscache->set($context->id, $contextextensions); 124 } 125 return $contextextensions; 126 } 127 128 /** 129 * Obtains a string with all supported extensions by active plugins. 130 * Mainly to use as filepicker options parameter. 131 * 132 * @param context $context Optional context to check (default null) 133 * @return string A string with all the extensions supported. 134 */ 135 public function get_supported_extensions_as_string(context $context = null) { 136 $supported = $this->load_context_supported_extensions($context); 137 $extensions = array_keys($supported); 138 return implode(',', $extensions); 139 } 140 141 /** 142 * Returns the file extension for a file. 143 * 144 * @param string $filename The name of the file 145 * @return string The extension of the file 146 */ 147 public function get_extension(string $filename) { 148 $dot = strrpos($filename, '.'); 149 if ($dot === false) { 150 return ''; 151 } 152 return strtolower(substr($filename, $dot)); 153 } 154 155 /** 156 * Get the first content bank plugin supports a file extension. 157 * 158 * @param string $extension Content file extension 159 * @param context $context $context Optional context to check (default null) 160 * @return string contenttype name supports the file extension or null if the extension is not supported by any allowed plugin. 161 */ 162 public function get_extension_supporter(string $extension, context $context = null): ?string { 163 $supporters = $this->load_context_supported_extensions($context); 164 if (array_key_exists($extension, $supporters)) { 165 return $supporters[$extension]; 166 } 167 return null; 168 } 169 170 /** 171 * Find the contents with %$search% in the contextid defined. 172 * If contextid and search are empty, all contents are returned. 173 * In all the cases, only the contents for the enabled contentbank-type plugins are returned. 174 * No content-type permissions are validated here. It is the caller responsability to check that the user can access to them. 175 * The only validation done here is, for each content, a call to the method $content->is_view_allowed(). 176 * 177 * @param string|null $search Optional string to search (for now it will search only into the name). 178 * @param int $contextid Optional contextid to search. 179 * @param array $contenttypenames Optional array with the list of content-type names to search. 180 * @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid. 181 */ 182 public function search_contents(?string $search = null, ?int $contextid = 0, ?array $contenttypenames = null): array { 183 global $DB; 184 185 $contents = []; 186 187 // Get only contents for enabled content-type plugins. 188 $contenttypes = []; 189 $enabledcontenttypes = $this->get_enabled_content_types(); 190 foreach ($enabledcontenttypes as $contenttypename) { 191 if (empty($contenttypenames) || in_array($contenttypename, $contenttypenames)) { 192 $contenttypes[] = "contenttype_$contenttypename"; 193 } 194 } 195 196 if (empty($contenttypes)) { 197 // Early return if there are no content-type plugins enabled. 198 return $contents; 199 } 200 201 list($sqlcontenttypes, $params) = $DB->get_in_or_equal($contenttypes, SQL_PARAMS_NAMED); 202 $sql = " contenttype $sqlcontenttypes "; 203 204 // Filter contents on this context (if defined). 205 if (!empty($contextid)) { 206 $params['contextid'] = $contextid; 207 $sql .= ' AND contextid = :contextid '; 208 } 209 210 // Search for contents having this string (if defined). 211 if (!empty($search)) { 212 $sql .= ' AND ' . $DB->sql_like('name', ':name', false, false); 213 $params['name'] = '%' . $DB->sql_like_escape($search) . '%'; 214 } 215 216 $records = $DB->get_records_select('contentbank_content', $sql, $params, 'name ASC'); 217 foreach ($records as $record) { 218 $content = $this->get_content_from_id($record->id); 219 if ($content->is_view_allowed()) { 220 $contents[] = $content; 221 } 222 } 223 224 return $contents; 225 } 226 227 228 /** 229 * Return all the context where a user has all the given capabilities. 230 * 231 * @param string $capability The capability the user needs to have. 232 * @param int|null $userid Optional userid. $USER by default. 233 * @return array Array of the courses and course categories where the user has the given capability. 234 */ 235 public function get_contexts_with_capabilities_by_user($capability = 'moodle/contentbank:access', $userid = null): array { 236 global $USER; 237 238 if (!$userid) { 239 $userid = $USER->id; 240 } 241 242 $categoriescache = \cache::make('core', 'contentbank_allowed_categories'); 243 $coursescache = \cache::make('core', 'contentbank_allowed_courses'); 244 245 $categories = $categoriescache->get($userid); 246 $courses = $coursescache->get($userid); 247 248 if ($categories === false || $courses === false) { 249 list($categories, $courses) = get_user_capability_contexts($capability, true, $userid, true, 250 'shortname, ctxlevel, ctxinstance, ctxid', 'name, ctxlevel, ctxinstance, ctxid', 'shortname', 'name'); 251 $categoriescache->set($userid, $categories); 252 $coursescache->set($userid, $courses); 253 } 254 255 return [$categories, $courses]; 256 } 257 258 /** 259 * Create content from a file information. 260 * 261 * @throws file_exception If file operations fail 262 * @throws dml_exception if the content creation fails 263 * @param \context $context Context where to upload the file and content. 264 * @param int $userid Id of the user uploading the file. 265 * @param stored_file $file The file to get information from 266 * @return content 267 */ 268 public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content { 269 global $USER; 270 if (empty($userid)) { 271 $userid = $USER->id; 272 } 273 // Get the contenttype to manage given file's extension. 274 $filename = $file->get_filename(); 275 $extension = $this->get_extension($filename); 276 $plugin = $this->get_extension_supporter($extension, $context); 277 $classname = '\\contenttype_'.$plugin.'\\contenttype'; 278 $record = new \stdClass(); 279 $record->name = $filename; 280 $record->usercreated = $userid; 281 $contentype = new $classname($context); 282 $content = $contentype->upload_content($file, $record); 283 $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content()); 284 $event->trigger(); 285 return $content; 286 } 287 288 /** 289 * Delete content bank content by context. 290 * 291 * @param context $context The context to delete content from. 292 * @return bool 293 */ 294 public function delete_contents(context $context): bool { 295 global $DB; 296 297 $result = true; 298 $records = $DB->get_records('contentbank_content', ['contextid' => $context->id]); 299 foreach ($records as $record) { 300 $content = $this->get_content_from_id($record->id); 301 $contenttype = $content->get_content_type_instance(); 302 if (!$contenttype->delete_content($content)) { 303 $result = false; 304 } 305 } 306 return $result; 307 } 308 309 /** 310 * Move content bank content from a context to another. 311 * 312 * @param context $from The context to get content from. 313 * @param context $to The context to move content to. 314 * @return bool 315 */ 316 public function move_contents(context $from, context $to): bool { 317 global $DB; 318 319 $result = true; 320 $records = $DB->get_records('contentbank_content', ['contextid' => $from->id]); 321 foreach ($records as $record) { 322 $content = $this->get_content_from_id($record->id); 323 $contenttype = $content->get_content_type_instance(); 324 if (!$contenttype->move_content($content, $to)) { 325 $result = false; 326 } 327 } 328 return $result; 329 } 330 331 /** 332 * Get the list of content types that have the requested feature. 333 * 334 * @param string $feature Feature code e.g CAN_UPLOAD. 335 * @param null|\context $context Optional context to check the permission to use the feature. 336 * @param bool $enabled Whether check only the enabled content types or all of them. 337 * 338 * @return string[] List of content types where the user has permission to access the feature. 339 */ 340 public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array { 341 $contenttypes = []; 342 // Check enabled content types or all of them. 343 if ($enabled) { 344 $contenttypestocheck = $this->get_enabled_content_types(); 345 } else { 346 $plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype'); 347 foreach ($plugins as $plugin) { 348 $contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype"; 349 $contenttypestocheck[$contenttypeclassname] = $plugin->name; 350 } 351 } 352 353 foreach ($contenttypestocheck as $classname => $name) { 354 $contenttype = new $classname($context); 355 // The method names that check the features permissions must follow the pattern can_feature. 356 if ($contenttype->{"can_$feature"}()) { 357 $contenttypes[$classname] = $name; 358 } 359 } 360 361 return $contenttypes; 362 } 363 364 /** 365 * Return a content class form a content id. 366 * 367 * @throws coding_exception if the ID is not valid or some class does no exists 368 * @param int $id the content id 369 * @return content the content class instance 370 */ 371 public function get_content_from_id(int $id): content { 372 global $DB; 373 $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST); 374 $contentclass = "\\$record->contenttype\\content"; 375 return new $contentclass($record); 376 } 377 378 /** 379 * Whether the context is allowed. 380 * 381 * @param context $context Context to check. 382 * @return bool 383 */ 384 public function is_context_allowed(context $context): bool { 385 return in_array($context->contextlevel, self::ALLOWED_CONTEXT_LEVELS); 386 } 387 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body