on-demand release 4.0dev+
[moodle.git] / contentbank / classes / content.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 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_text;
28 use stored_file;
29 use stdClass;
30 use coding_exception;
31 use context;
32 use moodle_url;
33 use core\event\contentbank_content_updated;
35 /**
36  * Content manager class
37  *
38  * @package    core_contentbank
39  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 abstract class content {
43     /**
44      * @var int Visibility value. Public content is visible to all users with access to the content bank of the
45      * appropriate context.
46      */
47     public const VISIBILITY_PUBLIC = 1;
49     /**
50      * @var int Visibility value. Unlisted content is only visible to the author and to users with
51      * moodle/contentbank:viewunlistedcontent capability.
52      */
53     public const VISIBILITY_UNLISTED = 2;
55     /** @var stdClass $content The content of the current instance. **/
56     protected $content  = null;
58     /**
59      * Content bank constructor
60      *
61      * @param stdClass $record A contentbank_content record.
62      * @throws coding_exception If content type is not right.
63      */
64     public function __construct(stdClass $record) {
65         // Content type should exist and be linked to plugin classname.
66         $classname = $record->contenttype.'\\content';
67         if (get_class($this) != $classname) {
68             throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
69         }
70         $typeclass = $record->contenttype.'\\contenttype';
71         if (!class_exists($typeclass)) {
72             throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
73         }
74         // A record with the id must exist in 'contentbank_content' table.
75         // To improve performance, we are only checking the id is set, but no querying the database.
76         if (!isset($record->id)) {
77             throw new coding_exception(get_string('invalidcontentid', 'error'));
78         }
79         $this->content = $record;
80     }
82     /**
83      * Returns $this->content.
84      *
85      * @return stdClass  $this->content.
86      */
87     public function get_content(): stdClass {
88         return $this->content;
89     }
91     /**
92      * Returns $this->content->contenttype.
93      *
94      * @return string  $this->content->contenttype.
95      */
96     public function get_content_type(): string {
97         return $this->content->contenttype;
98     }
100     /**
101      * Return the contenttype instance of this content.
102      *
103      * @return contenttype The content type instance
104      */
105     public function get_content_type_instance(): contenttype {
106         $context = context::instance_by_id($this->content->contextid);
107         $contenttypeclass = "\\{$this->content->contenttype}\\contenttype";
108         return new $contenttypeclass($context);
109     }
111     /**
112      * Returns $this->content->timemodified.
113      *
114      * @return int  $this->content->timemodified.
115      */
116     public function get_timemodified(): int {
117         return $this->content->timemodified;
118     }
120     /**
121      * Updates content_bank table with information in $this->content.
122      *
123      * @return boolean  True if the content has been succesfully updated. False otherwise.
124      * @throws \coding_exception if not loaded.
125      */
126     public function update_content(): bool {
127         global $USER, $DB;
129         // A record with the id must exist in 'contentbank_content' table.
130         // To improve performance, we are only checking the id is set, but no querying the database.
131         if (!isset($this->content->id)) {
132             throw new coding_exception(get_string('invalidcontentid', 'error'));
133         }
134         $this->content->usermodified = $USER->id;
135         $this->content->timemodified = time();
136         $result = $DB->update_record('contentbank_content', $this->content);
137         if ($result) {
138             // Trigger an event for updating this content.
139             $event = contentbank_content_updated::create_from_record($this->content);
140             $event->trigger();
141         }
142         return $result;
143     }
145     /**
146      * Set a new name to the content.
147      *
148      * @param string $name  The name of the content.
149      * @return bool  True if the content has been succesfully updated. False otherwise.
150      * @throws \coding_exception if not loaded.
151      */
152     public function set_name(string $name): bool {
153         $name = trim($name);
154         if ($name === '') {
155             return false;
156         }
158         // Clean name.
159         $name = clean_param($name, PARAM_TEXT);
160         if (core_text::strlen($name) > 255) {
161             $name = core_text::substr($name, 0, 255);
162         }
164         $oldname = $this->content->name;
165         $this->content->name = $name;
166         $updated = $this->update_content();
167         if (!$updated) {
168             $this->content->name = $oldname;
169         }
170         return $updated;
171     }
173     /**
174      * Returns the name of the content.
175      *
176      * @return string   The name of the content.
177      */
178     public function get_name(): string {
179         return $this->content->name;
180     }
182     /**
183      * Set a new contextid to the content.
184      *
185      * @param int $contextid  The new contextid of the content.
186      * @return bool  True if the content has been succesfully updated. False otherwise.
187      */
188     public function set_contextid(int $contextid): bool {
189         if ($this->content->contextid == $contextid) {
190             return true;
191         }
193         $oldcontextid = $this->content->contextid;
194         $this->content->contextid = $contextid;
195         $updated = $this->update_content();
196         if ($updated) {
197             // Move files to new context
198             $fs = get_file_storage();
199             $fs->move_area_files_to_new_context($oldcontextid, $contextid, 'contentbank', 'public', $this->content->id);
200         } else {
201             $this->content->contextid = $oldcontextid;
202         }
203         return $updated;
204     }
206     /**
207      * Returns the contextid of the content.
208      *
209      * @return int   The id of the content context.
210      */
211     public function get_contextid(): string {
212         return $this->content->contextid;
213     }
215     /**
216      * Returns the content ID.
217      *
218      * @return int   The content ID.
219      */
220     public function get_id(): int {
221         return $this->content->id;
222     }
224     /**
225      * Change the content instanceid value.
226      *
227      * @param int $instanceid    New instanceid for this content
228      * @return boolean           True if the instanceid has been succesfully updated. False otherwise.
229      */
230     public function set_instanceid(int $instanceid): bool {
231         $this->content->instanceid = $instanceid;
232         return $this->update_content();
233     }
235     /**
236      * Returns the $instanceid of this content.
237      *
238      * @return int   contentbank instanceid
239      */
240     public function get_instanceid(): int {
241         return $this->content->instanceid;
242     }
244     /**
245      * Change the content config values.
246      *
247      * @param string $configdata    New config information for this content
248      * @return boolean              True if the configdata has been succesfully updated. False otherwise.
249      */
250     public function set_configdata(string $configdata): bool {
251         $this->content->configdata = $configdata;
252         return $this->update_content();
253     }
255     /**
256      * Return the content config values.
257      *
258      * @return mixed   Config information for this content (json decoded)
259      */
260     public function get_configdata() {
261         return $this->content->configdata;
262     }
264     /**
265      * Sets a new content visibility and saves it to database.
266      *
267      * @param int $visibility Must be self::PUBLIC or self::UNLISTED
268      * @return bool
269      * @throws coding_exception
270      */
271     public function set_visibility(int $visibility): bool {
272         if (!in_array($visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED])) {
273             return false;
274         }
275         $this->content->visibility = $visibility;
276         return $this->update_content();
277     }
279     /**
280      * Return true if the content may be shown to other users in the content bank.
281      *
282      * @return boolean
283      */
284     public function get_visibility(): int {
285         return $this->content->visibility;
286     }
288     /**
289      * Import a file as a valid content.
290      *
291      * By default, all content has a public file area to interact with the content bank
292      * repository. This method should be overridden by contentypes which does not simply
293      * upload to the public file area.
294      *
295      * If any, the method will return the final stored_file. This way it can be invoked
296      * as parent::import_file in case any plugin want to store the file in the public area
297      * and also parse it.
298      *
299      * @throws file_exception If file operations fail
300      * @param stored_file $file File to store in the content file area.
301      * @return stored_file|null the stored content file or null if the file is discarted.
302      */
303     public function import_file(stored_file $file): ?stored_file {
304         $originalfile = $this->get_file();
305         if ($originalfile) {
306             $originalfile->replace_file_with($file);
307             return $originalfile;
308         } else {
309             $itemid = $this->get_id();
310             $fs = get_file_storage();
311             $filerecord = [
312                 'contextid' => $this->get_contextid(),
313                 'component' => 'contentbank',
314                 'filearea' => 'public',
315                 'itemid' => $this->get_id(),
316                 'filepath' => '/',
317                 'filename' => $file->get_filename(),
318                 'timecreated' => time(),
319             ];
320             return $fs->create_file_from_storedfile($filerecord, $file);
321         }
322     }
324     /**
325      * Returns the $file related to this content.
326      *
327      * @return stored_file  File stored in content bank area related to the given itemid.
328      * @throws \coding_exception if not loaded.
329      */
330     public function get_file(): ?stored_file {
331         $itemid = $this->get_id();
332         $fs = get_file_storage();
333         $files = $fs->get_area_files(
334             $this->content->contextid,
335             'contentbank',
336             'public',
337             $itemid,
338             'itemid, filepath, filename',
339             false
340         );
341         if (!empty($files)) {
342             $file = reset($files);
343             return $file;
344         }
345         return null;
346     }
348     /**
349      * Returns the places where the file associated to this content is used or an empty array if the content has no file.
350      *
351      * @return array of stored_file where current file content is used or empty array if it hasn't any file.
352      * @since 3.11
353      */
354     public function get_uses(): ?array {
355         $references = [];
357         $file = $this->get_file();
358         if ($file != null) {
359             $fs = get_file_storage();
360             $references = $fs->get_references_by_storedfile($file);
361         }
363         return $references;
364     }
366     /**
367      * Returns the file url related to this content.
368      *
369      * @return string       URL of the file stored in content bank area related to the given itemid.
370      * @throws \coding_exception if not loaded.
371      */
372     public function get_file_url(): string {
373         if (!$file = $this->get_file()) {
374             return '';
375         }
376         $fileurl = moodle_url::make_pluginfile_url(
377             $this->content->contextid,
378             'contentbank',
379             'public',
380             $file->get_itemid(),
381             $file->get_filepath(),
382             $file->get_filename()
383         );
385         return $fileurl;
386     }
388     /**
389      * Returns user has access permission for the content itself (based on what plugin needs).
390      *
391      * @return bool     True if content could be accessed. False otherwise.
392      */
393     public function is_view_allowed(): bool {
394         // Plugins can overwrite this method in case they want to check something related to content properties.
395         global $USER;
396         $context = \context::instance_by_id($this->get_contextid());
398         return $USER->id == $this->content->usercreated ||
399             $this->get_visibility() == self::VISIBILITY_PUBLIC ||
400             has_capability('moodle/contentbank:viewunlistedcontent', $context);
401     }