Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 * Create content from a file information. 229 * 230 * @throws file_exception If file operations fail 231 * @throws dml_exception if the content creation fails 232 * @param \context $context Context where to upload the file and content. 233 * @param int $userid Id of the user uploading the file. 234 * @param stored_file $file The file to get information from 235 * @return content 236 */ 237 public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content { 238 global $USER; 239 if (empty($userid)) { 240 $userid = $USER->id; 241 } 242 // Get the contenttype to manage given file's extension. 243 $filename = $file->get_filename(); 244 $extension = $this->get_extension($filename); 245 $plugin = $this->get_extension_supporter($extension, $context); 246 $classname = '\\contenttype_'.$plugin.'\\contenttype'; 247 $record = new \stdClass(); 248 $record->name = $filename; 249 $record->usercreated = $userid; 250 $contentype = new $classname($context); 251 $content = $contentype->upload_content($file, $record); 252 $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content()); 253 $event->trigger(); 254 return $content; 255 } 256 257 /** 258 * Delete content bank content by context. 259 * 260 * @param context $context The context to delete content from. 261 * @return bool 262 */ 263 public function delete_contents(context $context): bool { 264 global $DB; 265 266 $result = true; 267 $records = $DB->get_records('contentbank_content', ['contextid' => $context->id]); 268 foreach ($records as $record) { 269 $content = $this->get_content_from_id($record->id); 270 $contenttype = $content->get_content_type_instance(); 271 if (!$contenttype->delete_content($content)) { 272 $result = false; 273 } 274 } 275 return $result; 276 } 277 278 /** 279 * Move content bank content from a context to another. 280 * 281 * @param context $from The context to get content from. 282 * @param context $to The context to move content to. 283 * @return bool 284 */ 285 public function move_contents(context $from, context $to): bool { 286 global $DB; 287 288 $result = true; 289 $records = $DB->get_records('contentbank_content', ['contextid' => $from->id]); 290 foreach ($records as $record) { 291 $content = $this->get_content_from_id($record->id); 292 $contenttype = $content->get_content_type_instance(); 293 if (!$contenttype->move_content($content, $to)) { 294 $result = false; 295 } 296 } 297 return $result; 298 } 299 300 /** 301 * Get the list of content types that have the requested feature. 302 * 303 * @param string $feature Feature code e.g CAN_UPLOAD. 304 * @param null|\context $context Optional context to check the permission to use the feature. 305 * @param bool $enabled Whether check only the enabled content types or all of them. 306 * 307 * @return string[] List of content types where the user has permission to access the feature. 308 */ 309 public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array { 310 $contenttypes = []; 311 // Check enabled content types or all of them. 312 if ($enabled) { 313 $contenttypestocheck = $this->get_enabled_content_types(); 314 } else { 315 $plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype'); 316 foreach ($plugins as $plugin) { 317 $contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype"; 318 $contenttypestocheck[$contenttypeclassname] = $plugin->name; 319 } 320 } 321 322 foreach ($contenttypestocheck as $classname => $name) { 323 $contenttype = new $classname($context); 324 // The method names that check the features permissions must follow the pattern can_feature. 325 if ($contenttype->{"can_$feature"}()) { 326 $contenttypes[$classname] = $name; 327 } 328 } 329 330 return $contenttypes; 331 } 332 333 /** 334 * Return a content class form a content id. 335 * 336 * @throws coding_exception if the ID is not valid or some class does no exists 337 * @param int $id the content id 338 * @return content the content class instance 339 */ 340 public function get_content_from_id(int $id): content { 341 global $DB; 342 $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST); 343 $contentclass = "\\$record->contenttype\\content"; 344 return new $contentclass($record); 345 } 346 347 /** 348 * Whether the context is allowed. 349 * 350 * @param context $context Context to check. 351 * @return bool 352 */ 353 public function is_context_allowed(context $context): bool { 354 return in_array($context->contextlevel, self::ALLOWED_CONTEXT_LEVELS); 355 } 356 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body