Merge branch 'MDL-81457-main' of https://github.com/andrewnicols/moodle
[moodle.git] / contentbank / classes / content.php
blob583735ba815de787143f53f598cf6103e4910635
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.
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
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
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
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
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.
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.
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
61 * @param stdClass $record A contentbank_content record.
62 * @throws coding_exception If content type is not right.
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));
70 $typeclass = $record->contenttype.'\\contenttype';
71 if (!class_exists($typeclass)) {
72 throw new coding_exception(get_string('contenttypenotfound', 'error', $record->contenttype));
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'));
79 $this->content = $record;
82 /**
83 * Returns $this->content.
85 * @return stdClass $this->content.
87 public function get_content(): stdClass {
88 return $this->content;
91 /**
92 * Returns $this->content->contenttype.
94 * @return string $this->content->contenttype.
96 public function get_content_type(): string {
97 return $this->content->contenttype;
101 * Return the contenttype instance of this content.
103 * @return contenttype The content type instance
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);
112 * Returns $this->content->timemodified.
114 * @return int $this->content->timemodified.
116 public function get_timemodified(): int {
117 return $this->content->timemodified;
121 * Updates content_bank table with information in $this->content.
123 * @return boolean True if the content has been succesfully updated. False otherwise.
124 * @throws \coding_exception if not loaded.
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'));
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();
142 return $result;
146 * Set a new name to the content.
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.
152 public function set_name(string $name): bool {
153 $name = trim($name);
154 if ($name === '') {
155 return false;
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);
164 $oldname = $this->content->name;
165 $this->content->name = $name;
166 $updated = $this->update_content();
167 if (!$updated) {
168 $this->content->name = $oldname;
170 return $updated;
174 * Returns the name of the content.
176 * @return string The name of the content.
178 public function get_name(): string {
179 return $this->content->name;
183 * Set a new contextid to the content.
185 * @param int $contextid The new contextid of the content.
186 * @return bool True if the content has been succesfully updated. False otherwise.
188 public function set_contextid(int $contextid): bool {
189 if ($this->content->contextid == $contextid) {
190 return true;
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;
203 return $updated;
207 * Returns the contextid of the content.
209 * @return int The id of the content context.
211 public function get_contextid(): string {
212 return $this->content->contextid;
216 * Returns the content ID.
218 * @return int The content ID.
220 public function get_id(): int {
221 return $this->content->id;
225 * Change the content instanceid value.
227 * @param int $instanceid New instanceid for this content
228 * @return boolean True if the instanceid has been succesfully updated. False otherwise.
230 public function set_instanceid(int $instanceid): bool {
231 $this->content->instanceid = $instanceid;
232 return $this->update_content();
236 * Returns the $instanceid of this content.
238 * @return int contentbank instanceid
240 public function get_instanceid(): int {
241 return $this->content->instanceid;
245 * Change the content config values.
247 * @param string $configdata New config information for this content
248 * @return boolean True if the configdata has been succesfully updated. False otherwise.
250 public function set_configdata(string $configdata): bool {
251 $this->content->configdata = $configdata;
252 return $this->update_content();
256 * Return the content config values.
258 * @return mixed Config information for this content (json decoded)
260 public function get_configdata() {
261 return $this->content->configdata;
265 * Sets a new content visibility and saves it to database.
267 * @param int $visibility Must be self::PUBLIC or self::UNLISTED
268 * @return bool
269 * @throws coding_exception
271 public function set_visibility(int $visibility): bool {
272 if (!in_array($visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED])) {
273 return false;
275 $this->content->visibility = $visibility;
276 return $this->update_content();
280 * Return true if the content may be shown to other users in the content bank.
282 * @return boolean
284 public function get_visibility(): int {
285 return $this->content->visibility;
289 * Import a file as a valid content.
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.
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.
299 * @param stored_file $file File to store in the content file area.
300 * @return stored_file|null the stored content file or null if the file is discarted.
302 public function import_file(stored_file $file): ?stored_file {
303 $originalfile = $this->get_file();
304 if ($originalfile) {
305 $originalfile->replace_file_with($file);
306 return $originalfile;
307 } else {
308 $fs = get_file_storage();
309 $filerecord = [
310 'contextid' => $this->get_contextid(),
311 'component' => 'contentbank',
312 'filearea' => 'public',
313 'itemid' => $this->get_id(),
314 'filepath' => '/',
315 'filename' => $file->get_filename(),
316 'timecreated' => time(),
318 return $fs->create_file_from_storedfile($filerecord, $file);
323 * Returns the $file related to this content.
325 * @return stored_file File stored in content bank area related to the given itemid.
326 * @throws \coding_exception if not loaded.
328 public function get_file(): ?stored_file {
329 $itemid = $this->get_id();
330 $fs = get_file_storage();
331 $files = $fs->get_area_files(
332 $this->content->contextid,
333 'contentbank',
334 'public',
335 $itemid,
336 'itemid, filepath, filename',
337 false
339 if (!empty($files)) {
340 $file = reset($files);
341 return $file;
343 return null;
347 * Returns the places where the file associated to this content is used or an empty array if the content has no file.
349 * @return array of stored_file where current file content is used or empty array if it hasn't any file.
350 * @since 3.11
352 public function get_uses(): ?array {
353 $references = [];
355 $file = $this->get_file();
356 if ($file != null) {
357 $fs = get_file_storage();
358 $references = $fs->get_references_by_storedfile($file);
361 return $references;
365 * Returns the file url related to this content.
367 * @return string URL of the file stored in content bank area related to the given itemid.
368 * @throws \coding_exception if not loaded.
370 public function get_file_url(): string {
371 if (!$file = $this->get_file()) {
372 return '';
374 $fileurl = moodle_url::make_pluginfile_url(
375 $this->content->contextid,
376 'contentbank',
377 'public',
378 $file->get_itemid(),
379 $file->get_filepath(),
380 $file->get_filename()
383 return $fileurl;
387 * Returns user has access permission for the content itself (based on what plugin needs).
389 * @return bool True if content could be accessed. False otherwise.
391 public function is_view_allowed(): bool {
392 // Plugins can overwrite this method in case they want to check something related to content properties.
393 global $USER;
394 $context = \context::instance_by_id($this->get_contextid());
396 return $USER->id == $this->content->usercreated ||
397 $this->get_visibility() == self::VISIBILITY_PUBLIC ||
398 has_capability('moodle/contentbank:viewunlistedcontent', $context);