Merge branch 'MDL-69089-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE
[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 moodle_url;
32 use core\event\contentbank_content_updated;
34 /**
35  * Content manager class
36  *
37  * @package    core_contentbank
38  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
39  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  */
41 abstract class content {
43     /** @var stdClass $content The content of the current instance. **/
44     protected $content  = null;
46     /**
47      * Content bank constructor
48      *
49      * @param stdClass $record A contentbank_content record.
50      * @throws coding_exception If content type is not right.
51      */
52     public function __construct(stdClass $record) {
53         // Content type should exist and be linked to plugin classname.
54         $classname = $record->contenttype.'\\content';
55         if (get_class($this) != $classname) {
56             throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
57         }
58         $typeclass = $record->contenttype.'\\contenttype';
59         if (!class_exists($typeclass)) {
60             throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
61         }
62         // A record with the id must exist in 'contentbank_content' table.
63         // To improve performance, we are only checking the id is set, but no querying the database.
64         if (!isset($record->id)) {
65             throw new coding_exception(get_string('invalidcontentid', 'error'));
66         }
67         $this->content = $record;
68     }
70     /**
71      * Returns $this->content.
72      *
73      * @return stdClass  $this->content.
74      */
75     public function get_content(): stdClass {
76         return $this->content;
77     }
79     /**
80      * Returns $this->content->contenttype.
81      *
82      * @return string  $this->content->contenttype.
83      */
84     public function get_content_type(): string {
85         return $this->content->contenttype;
86     }
88     /**
89      * Returns $this->content->timemodified.
90      *
91      * @return int  $this->content->timemodified.
92      */
93     public function get_timemodified(): int {
94         return $this->content->timemodified;
95     }
97     /**
98      * Updates content_bank table with information in $this->content.
99      *
100      * @return boolean  True if the content has been succesfully updated. False otherwise.
101      * @throws \coding_exception if not loaded.
102      */
103     public function update_content(): bool {
104         global $USER, $DB;
106         // A record with the id must exist in 'contentbank_content' table.
107         // To improve performance, we are only checking the id is set, but no querying the database.
108         if (!isset($this->content->id)) {
109             throw new coding_exception(get_string('invalidcontentid', 'error'));
110         }
111         $this->content->usermodified = $USER->id;
112         $this->content->timemodified = time();
113         $result = $DB->update_record('contentbank_content', $this->content);
114         if ($result) {
115             // Trigger an event for updating this content.
116             $event = contentbank_content_updated::create_from_record($this->content);
117             $event->trigger();
118         }
119         return $result;
120     }
122     /**
123      * Set a new name to the content.
124      *
125      * @param string $name  The name of the content.
126      * @return bool  True if the content has been succesfully updated. False otherwise.
127      * @throws \coding_exception if not loaded.
128      */
129     public function set_name(string $name): bool {
130         $name = trim($name);
131         if (empty($name)) {
132             return false;
133         }
135         // Clean name.
136         $name = clean_param($name, PARAM_TEXT);
137         if (core_text::strlen($name) > 255) {
138             $name = core_text::substr($name, 0, 255);
139         }
141         $oldname = $this->content->name;
142         $this->content->name = $name;
143         $updated = $this->update_content();
144         if (!$updated) {
145             $this->content->name = $oldname;
146         }
147         return $updated;
148     }
150     /**
151      * Returns the name of the content.
152      *
153      * @return string   The name of the content.
154      */
155     public function get_name(): string {
156         return $this->content->name;
157     }
159     /**
160      * Set a new contextid to the content.
161      *
162      * @param int $contextid  The new contextid of the content.
163      * @return bool  True if the content has been succesfully updated. False otherwise.
164      */
165     public function set_contextid(int $contextid): bool {
166         if ($this->content->contextid == $contextid) {
167             return true;
168         }
170         $oldcontextid = $this->content->contextid;
171         $this->content->contextid = $contextid;
172         $updated = $this->update_content();
173         if ($updated) {
174             // Move files to new context
175             $fs = get_file_storage();
176             $fs->move_area_files_to_new_context($oldcontextid, $contextid, 'contentbank', 'public', $this->content->id);
177         } else {
178             $this->content->contextid = $oldcontextid;
179         }
180         return $updated;
181     }
183     /**
184      * Returns the contextid of the content.
185      *
186      * @return int   The id of the content context.
187      */
188     public function get_contextid(): string {
189         return $this->content->contextid;
190     }
192     /**
193      * Returns the content ID.
194      *
195      * @return int   The content ID.
196      */
197     public function get_id(): int {
198         return $this->content->id;
199     }
201     /**
202      * Change the content instanceid value.
203      *
204      * @param int $instanceid    New instanceid for this content
205      * @return boolean           True if the instanceid has been succesfully updated. False otherwise.
206      */
207     public function set_instanceid(int $instanceid): bool {
208         $this->content->instanceid = $instanceid;
209         return $this->update_content();
210     }
212     /**
213      * Returns the $instanceid of this content.
214      *
215      * @return int   contentbank instanceid
216      */
217     public function get_instanceid(): int {
218         return $this->content->instanceid;
219     }
221     /**
222      * Change the content config values.
223      *
224      * @param string $configdata    New config information for this content
225      * @return boolean              True if the configdata has been succesfully updated. False otherwise.
226      */
227     public function set_configdata(string $configdata): bool {
228         $this->content->configdata = $configdata;
229         return $this->update_content();
230     }
232     /**
233      * Return the content config values.
234      *
235      * @return mixed   Config information for this content (json decoded)
236      */
237     public function get_configdata() {
238         return $this->content->configdata;
239     }
241     /**
242      * Import a file as a valid content.
243      *
244      * By default, all content has a public file area to interact with the content bank
245      * repository. This method should be overridden by contentypes which does not simply
246      * upload to the public file area.
247      *
248      * If any, the method will return the final stored_file. This way it can be invoked
249      * as parent::import_file in case any plugin want to store the file in the public area
250      * and also parse it.
251      *
252      * @throws file_exception If file operations fail
253      * @param stored_file $file File to store in the content file area.
254      * @return stored_file|null the stored content file or null if the file is discarted.
255      */
256     public function import_file(stored_file $file): ?stored_file {
257         $originalfile = $this->get_file();
258         if ($originalfile) {
259             $originalfile->replace_file_with($file);
260             return $originalfile;
261         } else {
262             $itemid = $this->get_id();
263             $fs = get_file_storage();
264             $filerecord = [
265                 'contextid' => $this->get_contextid(),
266                 'component' => 'contentbank',
267                 'filearea' => 'public',
268                 'itemid' => $this->get_id(),
269                 'filepath' => '/',
270                 'filename' => $file->get_filename(),
271                 'timecreated' => time(),
272             ];
273             return $fs->create_file_from_storedfile($filerecord, $file);
274         }
275     }
277     /**
278      * Returns the $file related to this content.
279      *
280      * @return stored_file  File stored in content bank area related to the given itemid.
281      * @throws \coding_exception if not loaded.
282      */
283     public function get_file(): ?stored_file {
284         $itemid = $this->get_id();
285         $fs = get_file_storage();
286         $files = $fs->get_area_files(
287             $this->content->contextid,
288             'contentbank',
289             'public',
290             $itemid,
291             'itemid, filepath, filename',
292             false
293         );
294         if (!empty($files)) {
295             $file = reset($files);
296             return $file;
297         }
298         return null;
299     }
301     /**
302      * Returns the file url related to this content.
303      *
304      * @return string       URL of the file stored in content bank area related to the given itemid.
305      * @throws \coding_exception if not loaded.
306      */
307     public function get_file_url(): string {
308         if (!$file = $this->get_file()) {
309             return '';
310         }
311         $fileurl = moodle_url::make_pluginfile_url(
312             $this->content->contextid,
313             'contentbank',
314             'public',
315             $file->get_itemid(),
316             $file->get_filepath(),
317             $file->get_filename()
318         );
320         return $fileurl;
321     }
323     /**
324      * Returns user has access permission for the content itself (based on what plugin needs).
325      *
326      * @return bool     True if content could be accessed. False otherwise.
327      */
328     public function is_view_allowed(): bool {
329         // There's no capability at content level to check,
330         // but plugins can overwrite this method in case they want to check something related to content properties.
331         return true;
332     }