Merge branch 'MDL-68963-master' of git://github.com/bmbrands/moodle
[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     /** Plugin implements edition feature */
45     const CAN_EDIT = 'edit';
47     /** @var \context This contenttype's context. **/
48     protected $context = null;
50     /**
51      * Content type constructor
52      *
53      * @param \context $context Optional context to check (default null)
54      */
55     public function __construct(\context $context = null) {
56         if (empty($context)) {
57             $context = \context_system::instance();
58         }
59         $this->context = $context;
60     }
62     /**
63      * Fills content_bank table with appropiate information.
64      *
65      * @param \stdClass $record An optional content record compatible object (default null)
66      * @return content  Object with content bank information.
67      */
68     public function create_content(\stdClass $record = null): ?content {
69         global $USER, $DB;
71         $entry = new \stdClass();
72         $entry->contenttype = $this->get_contenttype_name();
73         $entry->contextid = $this->context->id;
74         $entry->name = $record->name ?? '';
75         $entry->usercreated = $record->usercreated ?? $USER->id;
76         $entry->timecreated = time();
77         $entry->usermodified = $entry->usercreated;
78         $entry->timemodified = $entry->timecreated;
79         $entry->configdata = $record->configdata ?? '';
80         $entry->instanceid = $record->instanceid ?? 0;
81         $entry->id = $DB->insert_record('contentbank_content', $entry);
82         if ($entry->id) {
83             $classname = '\\'.$entry->contenttype.'\\content';
84             $content = new $classname($entry);
85             // Trigger an event for creating the content.
86             $event = contentbank_content_created::create_from_record($content->get_content());
87             $event->trigger();
88             return $content;
89         }
90         return null;
91     }
93     /**
94      * Delete this content from the content_bank.
95      * This method can be overwritten by the plugins if they need to delete specific information.
96      *
97      * @param  content $content The content to delete.
98      * @return boolean true if the content has been deleted; false otherwise.
99      */
100     public function delete_content(content $content): bool {
101         global $DB;
103         // Delete the file if it exists.
104         if ($file = $content->get_file()) {
105             $file->delete();
106         }
108         // Delete the contentbank DB entry.
109         $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
110         if ($result) {
111             // Trigger an event for deleting this content.
112             $record = $content->get_content();
113             $event = contentbank_content_deleted::create([
114                 'objectid' => $content->get_id(),
115                 'relateduserid' => $record->usercreated,
116                 'context' => \context::instance_by_id($record->contextid),
117                 'other' => [
118                     'contenttype' => $content->get_content_type(),
119                     'name' => $content->get_name()
120                 ]
121             ]);
122             $event->add_record_snapshot('contentbank_content', $record);
123             $event->trigger();
124         }
125         return $result;
126     }
128     /**
129      * Rename this content from the content_bank.
130      * This method can be overwritten by the plugins if they need to change some other specific information.
131      *
132      * @param  content $content The content to rename.
133      * @param  string $name  The name of the content.
134      * @return boolean true if the content has been renamed; false otherwise.
135      */
136     public function rename_content(content $content, string $name): bool {
137         return $content->set_name($name);
138     }
140     /**
141      * Move content to another context.
142      * This method can be overwritten by the plugins if they need to change some other specific information.
143      *
144      * @param  content $content The content to rename.
145      * @param  \context $context  The new context.
146      * @return boolean true if the content has been renamed; false otherwise.
147      */
148     public function move_content(content $content, \context $context): bool {
149         return $content->set_contextid($context->id);
150     }
152     /**
153      * Returns the contenttype name of this content.
154      *
155      * @return string   Content type of the current instance
156      */
157     public function get_contenttype_name(): string {
158         $classname = get_class($this);
159         $contenttype = explode('\\', $classname);
160         return array_shift($contenttype);
161     }
163     /**
164      * Returns the plugin name of the current instance.
165      *
166      * @return string   Plugin name of the current instance
167      */
168     public function get_plugin_name(): string {
169         $contenttype = $this->get_contenttype_name();
170         $plugin = explode('_', $contenttype);
171         return array_pop($plugin);
172     }
174     /**
175      * Returns the URL where the content will be visualized.
176      *
177      * @param  content $content The content to be displayed.
178      * @return string           URL where to visualize the given content.
179      */
180     public function get_view_url(content $content): string {
181         return new moodle_url('/contentbank/view.php', ['id' => $content->get_id()]);
182     }
184     /**
185      * Returns the HTML content to add to view.php visualizer.
186      *
187      * @param  content $content The content to be displayed.
188      * @return string           HTML code to include in view.php.
189      */
190     public function get_view_content(content $content): string {
191         // Trigger an event for viewing this content.
192         $event = contentbank_content_viewed::create_from_record($content->get_content());
193         $event->trigger();
195         return '';
196     }
198     /**
199      * Returns the HTML code to render the icon for content bank contents.
200      *
201      * @param  content $content The content to be displayed.
202      * @return string               HTML code to render the icon
203      */
204     public function get_icon(content $content): string {
205         global $OUTPUT;
206         return $OUTPUT->image_url('f/unknown-64', 'moodle')->out(false);
207     }
209     /**
210      * Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
211      *
212      * @return bool     True if content could be accessed. False otherwise.
213      */
214     final public function can_access(): bool {
215         $classname = 'contenttype/'.$this->get_plugin_name();
216         $capability = $classname.":access";
217         $hascapabilities = has_capability('moodle/contentbank:access', $this->context)
218             && has_capability($capability, $this->context);
219         return $hascapabilities && $this->is_access_allowed();
220     }
222     /**
223      * Returns user has access capability for the content itself.
224      *
225      * @return bool     True if content could be accessed. False otherwise.
226      */
227     protected function is_access_allowed(): bool {
228         // Plugins can overwrite this function to add any check they need.
229         return true;
230     }
232     /**
233      * Returns the user has permission to upload new content.
234      *
235      * @return bool     True if content could be uploaded. False otherwise.
236      */
237     final public function can_upload(): bool {
238         if (!$this->is_feature_supported(self::CAN_UPLOAD)) {
239             return false;
240         }
241         if (!$this->can_access()) {
242             return false;
243         }
245         $classname = 'contenttype/'.$this->get_plugin_name();
246         $uploadcap = $classname.':upload';
247         $hascapabilities = has_capability('moodle/contentbank:upload', $this->context)
248             && has_capability($uploadcap, $this->context);
249         return $hascapabilities && $this->is_upload_allowed();
250     }
252     /**
253      * Returns plugin allows uploading.
254      *
255      * @return bool     True if plugin allows uploading. False otherwise.
256      */
257     protected function is_upload_allowed(): bool {
258         // Plugins can overwrite this function to add any check they need.
259         return true;
260     }
262     /**
263      * Check if the user can delete this content.
264      *
265      * @param  content $content The content to be deleted.
266      * @return bool True if content could be uploaded. False otherwise.
267      */
268     final public function can_delete(content $content): bool {
269         global $USER;
271         if ($this->context->id != $content->get_content()->contextid) {
272             // The content has to have exactly the same context as this contenttype.
273             return false;
274         }
276         $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
277         if ($content->get_content()->usercreated == $USER->id) {
278             // This content has been created by the current user; check if she can delete her content.
279             $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
280         }
282         return $hascapability && $this->is_delete_allowed($content);
283     }
285     /**
286      * Returns if content allows deleting.
287      *
288      * @param  content $content The content to be deleted.
289      * @return bool True if content allows uploading. False otherwise.
290      */
291     protected function is_delete_allowed(content $content): bool {
292         // Plugins can overwrite this function to add any check they need.
293         return true;
294     }
296     /**
297      * Check if the user can managed this content.
298      *
299      * @param  content $content The content to be managed.
300      * @return bool     True if content could be managed. False otherwise.
301      */
302     public final function can_manage(content $content): bool {
303         global $USER;
305         if ($this->context->id != $content->get_content()->contextid) {
306             // The content has to have exactly the same context as this contenttype.
307             return false;
308         }
310         // Check main contentbank management permission.
311         $hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
312         if ($content->get_content()->usercreated == $USER->id) {
313             // This content has been created by the current user; check if they can manage their content.
314             $hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
315         }
317         return $hascapability && $this->is_manage_allowed($content);
318     }
320     /**
321      * Returns if content allows managing.
322      *
323      * @param  content $content The content to be managed.
324      * @return bool True if content allows uploading. False otherwise.
325      */
326     protected function is_manage_allowed(content $content): bool {
327         // Plugins can overwrite this function to add any check they need.
328         return true;
329     }
331     /**
332      * Returns whether or not the user has permission to use the editor.
333      * This function will be called with the content to be edited as parameter,
334      * or null when is checking permission to create a new content using the editor.
335      *
336      * @param  content $content The content to be edited or null when creating a new content.
337      * @return bool     True if the user can edit content. False otherwise.
338      */
339     final public function can_edit(?content $content = null): bool {
340         if (!$this->is_feature_supported(self::CAN_EDIT)) {
341             return false;
342         }
344         if (!$this->can_access()) {
345             return false;
346         }
348         if (!is_null($content) && !$this->can_manage($content)) {
349             return false;
350         }
352         $classname = 'contenttype/'.$this->get_plugin_name();
354         $editioncap = $classname.':useeditor';
355         $hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
356         return $hascapabilities && $this->is_edit_allowed($content);
357     }
359     /**
360      * Returns plugin allows edition.
361      *
362      * @param  content $content The content to be edited.
363      * @return bool     True if plugin allows edition. False otherwise.
364      */
365     protected function is_edit_allowed(?content $content): bool {
366         // Plugins can overwrite this function to add any check they need.
367         return true;
368     }
370     /**
371      * Returns the plugin supports the feature.
372      *
373      * @param string $feature Feature code e.g CAN_UPLOAD
374      * @return bool     True if content could be uploaded. False otherwise.
375      */
376     final public function is_feature_supported(string $feature): bool {
377         return in_array($feature, $this->get_implemented_features());
378     }
380     /**
381      * Return an array of implemented features by the plugins.
382      *
383      * @return array
384      */
385     abstract protected function get_implemented_features(): array;
387     /**
388      * Return an array of extensions the plugins could manage.
389      *
390      * @return array
391      */
392     abstract public function get_manageable_extensions(): array;
394     /**
395      * Returns the list of different types of the given content type.
396      *
397      * A content type can have one or more options for creating content. This method will report all of them or only the content
398      * type itself if it has no other options.
399      *
400      * @return array An object for each type:
401      *     - string typename: descriptive name of the type.
402      *     - string typeeditorparams: params required by this content type editor.
403      *     - url typeicon: this type icon.
404      */
405     abstract public function get_contenttype_types(): array;