MDL-68483 contentbank: improve search API
[moodle.git] / contentbank / classes / contenttype.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 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  */
25 namespace core_contentbank;
27 use core\event\contentbank_content_created;
28 use core\event\contentbank_content_deleted;
29 use core\event\contentbank_content_viewed;
30 use moodle_url;
32 /**
33  * Content type manager class
34  *
35  * @package    core_contentbank
36  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 abstract class contenttype {
41     /** Plugin implements uploading feature */
42     const CAN_UPLOAD = 'upload';
44     /** @var context This contenttype's context. **/
45     protected $context = null;
47     /**
48      * Content type constructor
49      *
50      * @param \context $context Optional context to check (default null)
51      */
52     public function __construct(\context $context = null) {
53         if (empty($context)) {
54             $context = \context_system::instance();
55         }
56         $this->context = $context;
57     }
59     /**
60      * Fills content_bank table with appropiate information.
61      *
62      * @param stdClass $record An optional content record compatible object (default null)
63      * @return content  Object with content bank information.
64      */
65     public function create_content(\stdClass $record = null): ?content {
66         global $USER, $DB;
68         $entry = new \stdClass();
69         $entry->contenttype = $this->get_contenttype_name();
70         $entry->contextid = $this->context->id;
71         $entry->name = $record->name ?? '';
72         $entry->usercreated = $record->usercreated ?? $USER->id;
73         $entry->timecreated = time();
74         $entry->usermodified = $entry->usercreated;
75         $entry->timemodified = $entry->timecreated;
76         $entry->configdata = $record->configdata ?? '';
77         $entry->instanceid = $record->instanceid ?? 0;
78         $entry->id = $DB->insert_record('contentbank_content', $entry);
79         if ($entry->id) {
80             $classname = '\\'.$entry->contenttype.'\\content';
81             $content = new $classname($entry);
82             // Trigger an event for creating the content.
83             $event = contentbank_content_created::create_from_record($content->get_content());
84             $event->trigger();
85             return $content;
86         }
87         return null;
88     }
90     /**
91      * Delete this content from the content_bank.
92      * This method can be overwritten by the plugins if they need to delete specific information.
93      *
94      * @param  content $content The content to delete.
95      * @return boolean true if the content has been deleted; false otherwise.
96      */
97     public function delete_content(content $content): bool {
98         global $DB;
100         // Delete the file if it exists.
101         if ($file = $content->get_file()) {
102             $file->delete();
103         }
105         // Delete the contentbank DB entry.
106         $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
107         if ($result) {
108             // Trigger an event for deleting this content.
109             $record = $content->get_content();
110             $event = contentbank_content_deleted::create([
111                 'objectid' => $content->get_id(),
112                 'relateduserid' => $record->usercreated,
113                 'context' => \context::instance_by_id($record->contextid),
114                 'other' => [
115                     'contenttype' => $content->get_content_type(),
116                     'name' => $content->get_name()
117                 ]
118             ]);
119             $event->add_record_snapshot('contentbank_content', $record);
120             $event->trigger();
121         }
122         return $result;
123     }
125     /**
126      * Rename this content from the content_bank.
127      * This method can be overwritten by the plugins if they need to change some other specific information.
128      *
129      * @param  content $content The content to rename.
130      * @param string $name  The name of the content.
131      * @return boolean true if the content has been renamed; false otherwise.
132      */
133     public function rename_content(content $content, string $name): bool {
134         return $content->set_name($name);
135     }
137     /**
138      * Returns the contenttype name of this content.
139      *
140      * @return string   Content type of the current instance
141      */
142     public function get_contenttype_name(): string {
143         $classname = get_class($this);
144         $contenttype = explode('\\', $classname);
145         return array_shift($contenttype);
146     }
148     /**
149      * Returns the plugin name of the current instance.
150      *
151      * @return string   Plugin name of the current instance
152      */
153     public function get_plugin_name(): string {
154         $contenttype = $this->get_contenttype_name();
155         $plugin = explode('_', $contenttype);
156         return array_pop($plugin);
157     }
159     /**
160      * Returns the URL where the content will be visualized.
161      *
162      * @param stdClass $record  The content to be displayed.
163      * @return string           URL where to visualize the given content.
164      */
165     public function get_view_url(\stdClass $record): string {
166         return new moodle_url('/contentbank/view.php', ['id' => $record->id]);
167     }
169     /**
170      * Returns the HTML content to add to view.php visualizer.
171      *
172      * @param stdClass $record  The content to be displayed.
173      * @return string           HTML code to include in view.php.
174      */
175     public function get_view_content(\stdClass $record): string {
176         // Trigger an event for viewing this content.
177         $event = contentbank_content_viewed::create_from_record($record);
178         $event->trigger();
180         // Main contenttype class can visualize the content, but plugins could overwrite visualization.
181         return '';
182     }
184     /**
185      * Returns the HTML code to render the icon for content bank contents.
186      *
187      * @param string $contentname   The contentname to add as alt value to the icon.
188      * @return string               HTML code to render the icon
189      */
190     public function get_icon(string $contentname): string {
191         global $OUTPUT;
192         return $OUTPUT->pix_icon('f/unknown-64', $contentname, 'moodle', ['class' => 'iconsize-big']);
193     }
195     /**
196      * Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
197      *
198      * @return bool     True if content could be accessed. False otherwise.
199      */
200     final public function can_access(): bool {
201         $classname = 'contenttype/'.$this->get_plugin_name();
202         $capability = $classname.":access";
203         $hascapabilities = has_capability('moodle/contentbank:access', $this->context)
204             && has_capability($capability, $this->context);
205         return $hascapabilities && $this->is_access_allowed();
206     }
208     /**
209      * Returns user has access capability for the content itself.
210      *
211      * @return bool     True if content could be accessed. False otherwise.
212      */
213     protected function is_access_allowed(): bool {
214         // Plugins can overwrite this function to add any check they need.
215         return true;
216     }
218     /**
219      * Returns the user has permission to upload new content.
220      *
221      * @return bool     True if content could be uploaded. False otherwise.
222      */
223     final public function can_upload(): bool {
224         if (!$this->is_feature_supported(self::CAN_UPLOAD)) {
225             return false;
226         }
227         if (!$this->can_access()) {
228             return false;
229         }
231         $classname = 'contenttype/'.$this->get_plugin_name();
232         $uploadcap = $classname.':upload';
233         $hascapabilities = has_capability('moodle/contentbank:upload', $this->context)
234             && has_capability($uploadcap, $this->context);
235         return $hascapabilities && $this->is_upload_allowed();
236     }
238     /**
239      * Returns plugin allows uploading.
240      *
241      * @return bool     True if plugin allows uploading. False otherwise.
242      */
243     protected function is_upload_allowed(): bool {
244         // Plugins can overwrite this function to add any check they need.
245         return true;
246     }
248     /**
249      * Check if the user can delete this content.
250      *
251      * @param  content $content The content to be deleted.
252      * @return bool True if content could be uploaded. False otherwise.
253      */
254     final public function can_delete(content $content): bool {
255         global $USER;
257         if ($this->context->id != $content->get_content()->contextid) {
258             // The content has to have exactly the same context as this contenttype.
259             return false;
260         }
262         $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
263         if ($content->get_content()->usercreated == $USER->id) {
264             // This content has been created by the current user; check if she can delete her content.
265             $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
266         }
268         return $hascapability && $this->is_delete_allowed($content);
269     }
271     /**
272      * Returns if content allows deleting.
273      *
274      * @param  content $content The content to be deleted.
275      * @return bool True if content allows uploading. False otherwise.
276      */
277     protected function is_delete_allowed(content $content): bool {
278         // Plugins can overwrite this function to add any check they need.
279         return true;
280     }
282     /**
283      * Check if the user can managed this content.
284      *
285      * @param  content $content The content to be managed.
286      * @return bool     True if content could be managed. False otherwise.
287      */
288     public final function can_manage(content $content): bool {
289         global $USER;
291         if ($this->context->id != $content->get_content()->contextid) {
292             // The content has to have exactly the same context as this contenttype.
293             return false;
294         }
296         // Check main contentbank management permission.
297         $hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
298         if ($content->get_content()->usercreated == $USER->id) {
299             // This content has been created by the current user; check if they can manage their content.
300             $hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
301         }
303         return $hascapability && $this->is_manage_allowed($content);
304     }
306     /**
307      * Returns if content allows managing.
308      *
309      * @param  content $content The content to be managed.
310      * @return bool True if content allows uploading. False otherwise.
311      */
312     protected function is_manage_allowed(content $content): bool {
313         // Plugins can overwrite this function to add any check they need.
314         return true;
315     }
317     /**
318      * Returns the plugin supports the feature.
319      *
320      * @param string $feature Feature code e.g CAN_UPLOAD
321      * @return bool     True if content could be uploaded. False otherwise.
322      */
323     final public function is_feature_supported(string $feature): bool {
324         return in_array($feature, $this->get_implemented_features());
325     }
327     /**
328      * Return an array of implemented features by the plugins.
329      *
330      * @return array
331      */
332     abstract protected function get_implemented_features(): array;
334     /**
335      * Return an array of extensions the plugins could manage.
336      *
337      * @return array
338      */
339     abstract public function get_manageable_extensions(): array;