MDL-68483 contentbank: improve search API
[moodle.git] / contentbank / classes / contentbank.php
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/>.
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  */
25 namespace core_contentbank;
27 use stored_file;
29 /**
30  * Content bank class
31  *
32  * @package    core_contentbank
33  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class contentbank {
38     /**
39      * Obtains the list of core_contentbank_content objects currently active.
40      *
41      * The list does not include players which are disabled.
42      *
43      * @return string[] Array of contentbank contenttypes.
44      */
45     public function get_enabled_content_types(): array {
46         $enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
47         $types = [];
48         foreach ($enabledtypes as $name) {
49             $contenttypeclassname = "\\contenttype_$name\\contenttype";
50             $contentclassname = "\\contenttype_$name\\content";
51             if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
52                 $types[] = $name;
53             }
54         }
55         return $types;
56     }
58     /**
59      * Obtains an array of supported extensions by active plugins.
60      *
61      * @return array The array with all the extensions supported and the supporting plugin names.
62      */
63     public function load_all_supported_extensions(): array {
64         $extensionscache = \cache::make('core', 'contentbank_enabled_extensions');
65         $supportedextensions = $extensionscache->get('enabled_extensions');
66         if ($supportedextensions === false) {
67             // Load all enabled extensions.
68             $supportedextensions = [];
69             foreach ($this->get_enabled_content_types() as $type) {
70                 $classname = "\\contenttype_$type\\contenttype";
71                 $contenttype = new $classname;
72                 if ($contenttype->is_feature_supported($contenttype::CAN_UPLOAD)) {
73                     $extensions = $contenttype->get_manageable_extensions();
74                     foreach ($extensions as $extension) {
75                         if (array_key_exists($extension, $supportedextensions)) {
76                             $supportedextensions[$extension][] = $type;
77                         } else {
78                             $supportedextensions[$extension] = [$type];
79                         }
80                     }
81                 }
82             }
83             $extensionscache->set('enabled_extensions', $supportedextensions);
84         }
85         return $supportedextensions;
86     }
88     /**
89      * Obtains an array of supported extensions in the given context.
90      *
91      * @param \context $context Optional context to check (default null)
92      * @return array The array with all the extensions supported and the supporting plugin names.
93      */
94     public function load_context_supported_extensions(\context $context = null): array {
95         $extensionscache = \cache::make('core', 'contentbank_context_extensions');
97         $contextextensions = $extensionscache->get($context->id);
98         if ($contextextensions === false) {
99             $contextextensions = [];
100             $supportedextensions = $this->load_all_supported_extensions();
101             foreach ($supportedextensions as $extension => $types) {
102                 foreach ($types as $type) {
103                     $classname = "\\contenttype_$type\\contenttype";
104                     $contenttype = new $classname($context);
105                     if ($contenttype->can_upload()) {
106                         $contextextensions[$extension] = $type;
107                         break;
108                     }
109                 }
110             }
111             $extensionscache->set($context->id, $contextextensions);
112         }
113         return $contextextensions;
114     }
116     /**
117      * Obtains a string with all supported extensions by active plugins.
118      * Mainly to use as filepicker options parameter.
119      *
120      * @param \context $context   Optional context to check (default null)
121      * @return string A string with all the extensions supported.
122      */
123     public function get_supported_extensions_as_string(\context $context = null) {
124         $supported = $this->load_context_supported_extensions($context);
125         $extensions = array_keys($supported);
126         return implode(',', $extensions);
127     }
129     /**
130      * Returns the file extension for a file.
131      *
132      * @param  string $filename The name of the file
133      * @return string The extension of the file
134      */
135     public function get_extension(string $filename) {
136         $dot = strrpos($filename, '.');
137         if ($dot === false) {
138             return '';
139         }
140         return strtolower(substr($filename, $dot));
141     }
143     /**
144      * Get the first content bank plugin supports a file extension.
145      *
146      * @param string $extension Content file extension
147      * @param \context $context $context     Optional context to check (default null)
148      * @return string contenttype name supports the file extension or null if the extension is not supported by any allowed plugin.
149      */
150     public function get_extension_supporter(string $extension, \context $context = null): ?string {
151         $supporters = $this->load_context_supported_extensions($context);
152         if (array_key_exists($extension, $supporters)) {
153             return $supporters[$extension];
154         }
155         return null;
156     }
158     /**
159      * Find the contents with %$search% in the contextid defined.
160      * If contextid and search are empty, all contents are returned.
161      * In all the cases, only the contents for the enabled contentbank-type plugins are returned.
162      * No content-type permissions are validated here. It is the caller responsability to check that the user can access to them.
163      * The only validation done here is, for each content, a call to the method $content->is_view_allowed().
164      *
165      * @param  string|null $search Optional string to search (for now it will search only into the name).
166      * @param  int $contextid Optional contextid to search.
167      * @param  array $contenttypenames Optional array with the list of content-type names to search.
168      * @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid.
169      */
170     public function search_contents(?string $search = null, ?int $contextid = 0, ?array $contenttypenames = null): array {
171         global $DB;
173         $contents = [];
175         // Get only contents for enabled content-type plugins.
176         $contenttypes = [];
177         $enabledcontenttypes = $this->get_enabled_content_types();
178         foreach ($enabledcontenttypes as $contenttypename) {
179             if (empty($contenttypenames) || in_array($contenttypename, $contenttypenames)) {
180                 $contenttypes[] = "contenttype_$contenttypename";
181             }
182         }
184         if (empty($contenttypes)) {
185             // Early return if there are no content-type plugins enabled.
186             return $contents;
187         }
189         list($sqlcontenttypes, $params) = $DB->get_in_or_equal($contenttypes, SQL_PARAMS_NAMED);
190         $sql = " contenttype $sqlcontenttypes ";
192         // Filter contents on this context (if defined).
193         if (!empty($contextid)) {
194             $params['contextid'] = $contextid;
195             $sql .= ' AND contextid = :contextid ';
196         }
198         // Search for contents having this string (if defined).
199         if (!empty($search)) {
200             $sql .= ' AND ' . $DB->sql_like('name', ':name', false, false);
201             $params['name'] = '%' . $DB->sql_like_escape($search) . '%';
202         }
204         $records = $DB->get_records_select('contentbank_content', $sql, $params, 'name ASC');
205         foreach ($records as $record) {
206             $contentclass = "\\$record->contenttype\\content";
207             $content = new $contentclass($record);
208             if ($content->is_view_allowed()) {
209                 $contents[] = $content;
210             }
211         }
213         return $contents;
214     }
216     /**
217      * Create content from a file information.
218      *
219      * @param \context $context Context where to upload the file and content.
220      * @param int $userid Id of the user uploading the file.
221      * @param stored_file $file The file to get information from
222      * @return content
223      */
224     public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content {
225         global $USER;
226         if (empty($userid)) {
227             $userid = $USER->id;
228         }
229         // Get the contenttype to manage given file's extension.
230         $filename = $file->get_filename();
231         $extension = $this->get_extension($filename);
232         $plugin = $this->get_extension_supporter($extension, $context);
233         $classname = '\\contenttype_'.$plugin.'\\contenttype';
234         $record = new \stdClass();
235         $record->name = $filename;
236         $record->usercreated = $userid;
237         $contentype = new $classname($context);
238         $content = $contentype->create_content($record);
239         $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content());
240         $event->trigger();
241         return $content;
242     }