on-demand release 4.0dev+
[moodle.git] / contentbank / classes / content.php
CommitLineData
33b8ca26
AA
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/>.
16
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 */
24
25namespace core_contentbank;
26
448012c6 27use core_text;
33b8ca26
AA
28use stored_file;
29use stdClass;
30use coding_exception;
c5a8469a 31use context;
33b8ca26 32use moodle_url;
db15dab8 33use core\event\contentbank_content_updated;
33b8ca26
AA
34
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 */
42abstract class content {
3776fbaf
FM
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;
48
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;
33b8ca26
AA
54
55 /** @var stdClass $content The content of the current instance. **/
56 protected $content = null;
57
58 /**
59 * Content bank constructor
60 *
de322e3d 61 * @param stdClass $record A contentbank_content record.
33b8ca26
AA
62 * @throws coding_exception If content type is not right.
63 */
de322e3d 64 public function __construct(stdClass $record) {
33b8ca26 65 // Content type should exist and be linked to plugin classname.
de322e3d 66 $classname = $record->contenttype.'\\content';
33b8ca26 67 if (get_class($this) != $classname) {
de322e3d 68 throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
33b8ca26 69 }
de322e3d 70 $typeclass = $record->contenttype.'\\contenttype';
33b8ca26 71 if (!class_exists($typeclass)) {
de322e3d 72 throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
33b8ca26 73 }
061893d8 74 // A record with the id must exist in 'contentbank_content' table.
33b8ca26 75 // To improve performance, we are only checking the id is set, but no querying the database.
de322e3d 76 if (!isset($record->id)) {
33b8ca26
AA
77 throw new coding_exception(get_string('invalidcontentid', 'error'));
78 }
de322e3d 79 $this->content = $record;
33b8ca26
AA
80 }
81
82 /**
83 * Returns $this->content.
84 *
85 * @return stdClass $this->content.
86 */
87 public function get_content(): stdClass {
88 return $this->content;
89 }
90
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 }
99
c5a8469a
FR
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 }
110
e695d9d0
BB
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 }
119
33b8ca26
AA
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;
128
061893d8 129 // A record with the id must exist in 'contentbank_content' table.
33b8ca26
AA
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();
fb302d38
AA
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;
33b8ca26
AA
143 }
144
448012c6
AA
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 {
5ea98dc5 153 $name = trim($name);
9b8c45e0 154 if ($name === '') {
448012c6
AA
155 return false;
156 }
157
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 }
163
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 }
172
33b8ca26
AA
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 }
181
f2a9bb6e
AA
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 }
192
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 }
205
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 }
214
33b8ca26
AA
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 }
223
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 }
234
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 }
243
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 }
254
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 }
263
3776fbaf
FM
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 }
278
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 }
287
3dfbd5a1
FR
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 }
323
33b8ca26
AA
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 }
347
890d0690
SA
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 = [];
356
357 $file = $this->get_file();
358 if ($file != null) {
359 $fs = get_file_storage();
360 $references = $fs->get_references_by_storedfile($file);
361 }
362
363 return $references;
364 }
365
33b8ca26
AA
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 );
384
385 return $fileurl;
386 }
387
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 */
fb445c76 393 public function is_view_allowed(): bool {
3776fbaf
FM
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());
397
398 return $USER->id == $this->content->usercreated ||
399 $this->get_visibility() == self::VISIBILITY_PUBLIC ||
400 has_capability('moodle/contentbank:viewunlistedcontent', $context);
33b8ca26
AA
401 }
402}