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 /** 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 'fullname, ctxlevel, ctxinstance, ctxid', 'name, ctxlevel, ctxinstance, ctxid', 'fullname', '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 * @param \context $context Context where to upload the file and content. 262 * @param int $userid Id of the user uploading the file. 263 * @param stored_file $file The file to get information from 264 * @return content 265 */ 266 public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content { 267 global $USER; 268 if (empty($userid)) { 269 $userid = $USER->id; 270 } 271 // Get the contenttype to manage given file's extension. 272 $filename = $file->get_filename(); 273 $extension = $this->get_extension($filename); 274 $plugin = $this->get_extension_supporter($extension, $context); 275 $classname = '\\contenttype_'.$plugin.'\\contenttype'; 276 $record = new \stdClass(); 277 $record->name = $filename; 278 $record->usercreated = $userid; 279 $contentype = new $classname($context); 280 $content = $contentype->upload_content($file, $record); 281 $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content()); 282 $event->trigger(); 283 return $content; 284 } 285 286 /** 287 * Delete content bank content by context. 288 * 289 * @param context $context The context to delete content from. 290 * @return bool 291 */ 292 public function delete_contents(context $context): bool { 293 global $DB; 294 295 $result = true; 296 $records = $DB->get_records('contentbank_content', ['contextid' => $context->id]); 297 foreach ($records as $record) { 298 $content = $this->get_content_from_id($record->id); 299 $contenttype = $content->get_content_type_instance(); 300 if (!$contenttype->delete_content($content)) { 301 $result = false; 302 } 303 } 304 return $result; 305 } 306 307 /** 308 * Move content bank content from a context to another. 309 * 310 * @param context $from The context to get content from. 311 * @param context $to The context to move content to. 312 * @return bool 313 */ 314 public function move_contents(context $from, context $to): bool { 315 global $DB; 316 317 $result = true; 318 $records = $DB->get_records('contentbank_content', ['contextid' => $from->id]); 319 foreach ($records as $record) { 320 $content = $this->get_content_from_id($record->id); 321 $contenttype = $content->get_content_type_instance(); 322 if (!$contenttype->move_content($content, $to)) { 323 $result = false; 324 } 325 } 326 return $result; 327 } 328 329 /** 330 * Get the list of content types that have the requested feature. 331 * 332 * @param string $feature Feature code e.g CAN_UPLOAD. 333 * @param null|\context $context Optional context to check the permission to use the feature. 334 * @param bool $enabled Whether check only the enabled content types or all of them. 335 * 336 * @return string[] List of content types where the user has permission to access the feature. 337 */ 338 public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array { 339 $contenttypes = []; 340 // Check enabled content types or all of them. 341 if ($enabled) { 342 $contenttypestocheck = $this->get_enabled_content_types(); 343 } else { 344 $plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype'); 345 foreach ($plugins as $plugin) { 346 $contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype"; 347 $contenttypestocheck[$contenttypeclassname] = $plugin->name; 348 } 349 } 350 351 foreach ($contenttypestocheck as $classname => $name) { 352 $contenttype = new $classname($context); 353 // The method names that check the features permissions must follow the pattern can_feature. 354 if ($contenttype->{"can_$feature"}()) { 355 $contenttypes[$classname] = $name; 356 } 357 } 358 359 return $contenttypes; 360 } 361 362 /** 363 * Return a content class form a content id. 364 * 365 * @throws coding_exception if the ID is not valid or some class does no exists 366 * @param int $id the content id 367 * @return content the content class instance 368 */ 369 public function get_content_from_id(int $id): content { 370 global $DB; 371 $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST); 372 $contentclass = "\\$record->contenttype\\content"; 373 return new $contentclass($record); 374 } 375 376 /** 377 * Whether the context is allowed. 378 * 379 * @param context $context Context to check. 380 * @return bool 381 */ 382 public function is_context_allowed(context $context): bool { 383 return in_array($context->contextlevel, self::ALLOWED_CONTEXT_LEVELS); 384 } 385 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body