Merge branch 'MDL-65847-auth_db_error_handling' of git://github.com/leonstr/moodle...
[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         if (empty($name)) {
131             return false;
132         }
134         // Clean name.
135         $name = clean_param($name, PARAM_TEXT);
136         if (core_text::strlen($name) > 255) {
137             $name = core_text::substr($name, 0, 255);
138         }
140         $oldname = $this->content->name;
141         $this->content->name = $name;
142         $updated = $this->update_content();
143         if (!$updated) {
144             $this->content->name = $oldname;
145         }
146         return $updated;
147     }
149     /**
150      * Returns the name of the content.
151      *
152      * @return string   The name of the content.
153      */
154     public function get_name(): string {
155         return $this->content->name;
156     }
158     /**
159      * Set a new contextid to the content.
160      *
161      * @param int $contextid  The new contextid of the content.
162      * @return bool  True if the content has been succesfully updated. False otherwise.
163      */
164     public function set_contextid(int $contextid): bool {
165         if ($this->content->contextid == $contextid) {
166             return true;
167         }
169         $oldcontextid = $this->content->contextid;
170         $this->content->contextid = $contextid;
171         $updated = $this->update_content();
172         if ($updated) {
173             // Move files to new context
174             $fs = get_file_storage();
175             $fs->move_area_files_to_new_context($oldcontextid, $contextid, 'contentbank', 'public', $this->content->id);
176         } else {
177             $this->content->contextid = $oldcontextid;
178         }
179         return $updated;
180     }
182     /**
183      * Returns the contextid of the content.
184      *
185      * @return int   The id of the content context.
186      */
187     public function get_contextid(): string {
188         return $this->content->contextid;
189     }
191     /**
192      * Returns the content ID.
193      *
194      * @return int   The content ID.
195      */
196     public function get_id(): int {
197         return $this->content->id;
198     }
200     /**
201      * Change the content instanceid value.
202      *
203      * @param int $instanceid    New instanceid for this content
204      * @return boolean           True if the instanceid has been succesfully updated. False otherwise.
205      */
206     public function set_instanceid(int $instanceid): bool {
207         $this->content->instanceid = $instanceid;
208         return $this->update_content();
209     }
211     /**
212      * Returns the $instanceid of this content.
213      *
214      * @return int   contentbank instanceid
215      */
216     public function get_instanceid(): int {
217         return $this->content->instanceid;
218     }
220     /**
221      * Change the content config values.
222      *
223      * @param string $configdata    New config information for this content
224      * @return boolean              True if the configdata has been succesfully updated. False otherwise.
225      */
226     public function set_configdata(string $configdata): bool {
227         $this->content->configdata = $configdata;
228         return $this->update_content();
229     }
231     /**
232      * Return the content config values.
233      *
234      * @return mixed   Config information for this content (json decoded)
235      */
236     public function get_configdata() {
237         return $this->content->configdata;
238     }
240     /**
241      * Import a file as a valid content.
242      *
243      * By default, all content has a public file area to interact with the content bank
244      * repository. This method should be overridden by contentypes which does not simply
245      * upload to the public file area.
246      *
247      * If any, the method will return the final stored_file. This way it can be invoked
248      * as parent::import_file in case any plugin want to store the file in the public area
249      * and also parse it.
250      *
251      * @throws file_exception If file operations fail
252      * @param stored_file $file File to store in the content file area.
253      * @return stored_file|null the stored content file or null if the file is discarted.
254      */
255     public function import_file(stored_file $file): ?stored_file {
256         $originalfile = $this->get_file();
257         if ($originalfile) {
258             $originalfile->replace_file_with($file);
259             return $originalfile;
260         } else {
261             $itemid = $this->get_id();
262             $fs = get_file_storage();
263             $filerecord = [
264                 'contextid' => $this->get_contextid(),
265                 'component' => 'contentbank',
266                 'filearea' => 'public',
267                 'itemid' => $this->get_id(),
268                 'filepath' => '/',
269                 'filename' => $file->get_filename(),
270                 'timecreated' => time(),
271             ];
272             return $fs->create_file_from_storedfile($filerecord, $file);
273         }
274     }
276     /**
277      * Returns the $file related to this content.
278      *
279      * @return stored_file  File stored in content bank area related to the given itemid.
280      * @throws \coding_exception if not loaded.
281      */
282     public function get_file(): ?stored_file {
283         $itemid = $this->get_id();
284         $fs = get_file_storage();
285         $files = $fs->get_area_files(
286             $this->content->contextid,
287             'contentbank',
288             'public',
289             $itemid,
290             'itemid, filepath, filename',
291             false
292         );
293         if (!empty($files)) {
294             $file = reset($files);
295             return $file;
296         }
297         return null;
298     }
300     /**
301      * Returns the file url related to this content.
302      *
303      * @return string       URL of the file stored in content bank area related to the given itemid.
304      * @throws \coding_exception if not loaded.
305      */
306     public function get_file_url(): string {
307         if (!$file = $this->get_file()) {
308             return '';
309         }
310         $fileurl = moodle_url::make_pluginfile_url(
311             $this->content->contextid,
312             'contentbank',
313             'public',
314             $file->get_itemid(),
315             $file->get_filepath(),
316             $file->get_filename()
317         );
319         return $fileurl;
320     }
322     /**
323      * Returns user has access permission for the content itself (based on what plugin needs).
324      *
325      * @return bool     True if content could be accessed. False otherwise.
326      */
327     public function is_view_allowed(): bool {
328         // There's no capability at content level to check,
329         // but plugins can overwrite this method in case they want to check something related to content properties.
330         return true;
331     }