Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
namespace core_contentbank;
18
 
19
use core\event\contentbank_content_created;
20
use core\event\contentbank_content_deleted;
21
use core\event\contentbank_content_viewed;
22
use stored_file;
23
use moodle_url;
24
 
25
/**
26
 * Content type manager class
27
 *
28
 * @package    core_contentbank
29
 * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
abstract class contenttype {
33
 
34
    /** @var string Constant representing whether the plugin implements uploading feature */
35
    const CAN_UPLOAD = 'upload';
36
 
37
    /** @var string Constant representing whether the plugin implements edition feature */
38
    const CAN_EDIT = 'edit';
39
 
40
    /**
41
     * @var string Constant representing whether the plugin implements download feature
42
     * @since  Moodle 3.10
43
     */
44
    const CAN_DOWNLOAD = 'download';
45
 
46
    /**
47
     * @var string Constant representing whether the plugin implements copy feature
48
     * @since  Moodle 4.3
49
     */
50
    const CAN_COPY = 'copy';
51
 
52
    /** @var \context This contenttype's context. **/
53
    protected $context = null;
54
 
55
    /**
56
     * Content type constructor
57
     *
58
     * @param \context $context Optional context to check (default null)
59
     */
1441 ariadna 60
    public function __construct(?\context $context = null) {
1 efrain 61
        if (empty($context)) {
62
            $context = \context_system::instance();
63
        }
64
        $this->context = $context;
65
    }
66
 
67
    /**
68
     * Fills content_bank table with appropiate information.
69
     *
70
     * @throws dml_exception A DML specific exception is thrown for any creation error.
71
     * @param \stdClass $record An optional content record compatible object (default null)
72
     * @return content  Object with content bank information.
73
     */
1441 ariadna 74
    public function create_content(?\stdClass $record = null): content {
1 efrain 75
        global $USER, $DB, $CFG;
76
 
77
        $entry = new \stdClass();
78
        if (isset($record->visibility)) {
79
            $entry->visibility = $record->visibility;
80
        } else {
81
            $usercreated = $record->usercreated ?? $USER->id;
82
            $entry->visibility = get_user_preferences('core_contentbank_visibility',
83
                $CFG->defaultpreference_core_contentbank_visibility, $usercreated);
84
        }
85
        $entry->contenttype = $this->get_contenttype_name();
86
        $entry->contextid = $this->context->id;
87
        $entry->name = $record->name ?? '';
88
        $entry->usercreated = $record->usercreated ?? $USER->id;
89
        $entry->timecreated = time();
90
        $entry->usermodified = $entry->usercreated;
91
        $entry->timemodified = $entry->timecreated;
92
        $entry->configdata = $record->configdata ?? '';
93
        $entry->instanceid = $record->instanceid ?? 0;
94
        $entry->id = $DB->insert_record('contentbank_content', $entry);
95
        $classname = '\\'.$entry->contenttype.'\\content';
96
        $content = new $classname($entry);
97
        // Trigger an event for creating the content.
98
        $event = contentbank_content_created::create_from_record($content->get_content());
99
        $event->trigger();
100
        return $content;
101
    }
102
 
103
    /**
104
     * Create a new content from an uploaded file.
105
     *
106
     * @throws file_exception If file operations fail
107
     * @throws dml_exception if the content creation fails
108
     * @param stored_file $file the uploaded file
109
     * @param \stdClass|null $record an optional content record
110
     * @return content  Object with content bank information.
111
     */
1441 ariadna 112
    public function upload_content(stored_file $file, ?\stdClass $record = null): content {
1 efrain 113
        if (empty($record)) {
114
            $record = new \stdClass();
115
            $record->name = $file->get_filename();
116
        }
117
        $content = $this->create_content($record);
118
        try {
119
            $content->import_file($file);
120
        } catch (\moodle_exception $e) {
121
            $this->delete_content($content);
122
            throw new \moodle_exception($e->errorcode);
123
        }
124
 
125
        return $content;
126
    }
127
 
128
    /**
129
     * Replace a content using an uploaded file.
130
     *
131
     * @throws file_exception If file operations fail
132
     * @throws dml_exception if the content creation fails
133
     * @param stored_file $file the uploaded file
134
     * @param content $content the original content record
135
     * @return content Object with the updated content bank information.
136
     */
137
    public function replace_content(stored_file $file, content $content): content {
138
        $content->import_file($file);
139
        $content->update_content();
140
        return $content;
141
    }
142
 
143
    /**
144
     * Delete this content from the content_bank.
145
     * This method can be overwritten by the plugins if they need to delete specific information.
146
     *
147
     * @param  content $content The content to delete.
148
     * @return boolean true if the content has been deleted; false otherwise.
149
     */
150
    public function delete_content(content $content): bool {
151
        global $DB;
152
 
153
        // Delete the file if it exists.
154
        if ($file = $content->get_file()) {
155
            $file->delete();
156
        }
157
 
158
        // Delete the contentbank DB entry.
159
        $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
160
        if ($result) {
161
            // Trigger an event for deleting this content.
162
            $record = $content->get_content();
163
            $event = contentbank_content_deleted::create([
164
                'objectid' => $content->get_id(),
165
                'relateduserid' => $record->usercreated,
166
                'context' => \context::instance_by_id($record->contextid),
167
                'other' => [
168
                    'contenttype' => $content->get_content_type(),
169
                    'name' => $content->get_name()
170
                ]
171
            ]);
172
            $event->add_record_snapshot('contentbank_content', $record);
173
            $event->trigger();
174
        }
175
        return $result;
176
    }
177
 
178
    /**
179
     * Rename this content from the content_bank.
180
     * This method can be overwritten by the plugins if they need to change some other specific information.
181
     *
182
     * @param  content $content The content to rename.
183
     * @param  string $name  The name of the content.
184
     * @return boolean true if the content has been renamed; false otherwise.
185
     */
186
    public function rename_content(content $content, string $name): bool {
187
        return $content->set_name($name);
188
    }
189
 
190
    /**
191
     * Move content to another context.
192
     * This method can be overwritten by the plugins if they need to change some other specific information.
193
     *
194
     * @param  content $content The content to rename.
195
     * @param  \context $context  The new context.
196
     * @return boolean true if the content has been renamed; false otherwise.
197
     */
198
    public function move_content(content $content, \context $context): bool {
199
        return $content->set_contextid($context->id);
200
    }
201
 
202
    /**
203
     * Returns the contenttype name of this content.
204
     *
205
     * @return string   Content type of the current instance
206
     */
207
    public function get_contenttype_name(): string {
208
        $classname = get_class($this);
209
        $contenttype = explode('\\', $classname);
210
        return array_shift($contenttype);
211
    }
212
 
213
    /**
214
     * Returns the plugin name of the current instance.
215
     *
216
     * @return string   Plugin name of the current instance
217
     */
218
    public function get_plugin_name(): string {
219
        $contenttype = $this->get_contenttype_name();
220
        $plugin = explode('_', $contenttype);
221
        return array_pop($plugin);
222
    }
223
 
224
    /**
225
     * Returns the URL where the content will be visualized.
226
     *
227
     * @param  content $content The content to be displayed.
228
     * @return string           URL where to visualize the given content.
229
     */
230
    public function get_view_url(content $content): string {
231
        return new moodle_url('/contentbank/view.php', ['id' => $content->get_id()]);
232
    }
233
 
234
    /**
235
     * Returns the HTML content to add to view.php visualizer.
236
     *
237
     * @param  content $content The content to be displayed.
238
     * @return string           HTML code to include in view.php.
239
     */
240
    public function get_view_content(content $content): string {
1441 ariadna 241
        global $PAGE;
242
 
1 efrain 243
        // Trigger an event for viewing this content.
244
        $event = contentbank_content_viewed::create_from_record($content->get_content());
245
        $event->trigger();
246
 
1441 ariadna 247
        if ($content->has_custom_fields()) {
248
            $renderer = $PAGE->get_renderer('core');
249
            $renderable = new \core_contentbank\output\customfields($content);
250
            return $renderer->render($renderable);
251
        }
1 efrain 252
        return '';
253
    }
254
 
255
    /**
256
     * Returns the URL to download the content.
257
     *
258
     * @since  Moodle 3.10
259
     * @param  content $content The content to be downloaded.
260
     * @return string           URL with the content to download.
261
     */
262
    public function get_download_url(content $content): string {
263
        $downloadurl = '';
264
        $file = $content->get_file();
265
        if (!empty($file)) {
266
            $url = \moodle_url::make_pluginfile_url(
267
                $file->get_contextid(),
268
                $file->get_component(),
269
                $file->get_filearea(),
270
                $file->get_itemid(),
271
                $file->get_filepath(),
272
                $file->get_filename()
273
            );
274
            $downloadurl = $url->out(false);
275
        }
276
 
277
        return $downloadurl;
278
    }
279
 
280
    /**
281
     * Returns the HTML code to render the icon for content bank contents.
282
     *
283
     * @param  content $content The content to be displayed.
284
     * @return string               HTML code to render the icon
285
     */
286
    public function get_icon(content $content): string {
287
        global $OUTPUT;
288
        return $OUTPUT->image_url('f/unknown')->out(false);
289
    }
290
 
291
    /**
292
     * Returns user has access capability for the main content bank and the content itself (base on is_access_allowed from plugin).
293
     *
294
     * @return bool     True if content could be accessed. False otherwise.
295
     */
296
    final public function can_access(): bool {
297
        $classname = 'contenttype/'.$this->get_plugin_name();
298
        $capability = $classname.":access";
299
        $hascapabilities = has_capability('moodle/contentbank:access', $this->context)
300
            && has_capability($capability, $this->context);
301
        return $hascapabilities && $this->is_access_allowed();
302
    }
303
 
304
    /**
305
     * Returns user has access capability for the content itself.
306
     *
307
     * @return bool     True if content could be accessed. False otherwise.
308
     */
309
    protected function is_access_allowed(): bool {
310
        // Plugins can overwrite this function to add any check they need.
311
        return true;
312
    }
313
 
314
    /**
315
     * Returns the user has permission to upload new content.
316
     *
317
     * @return bool     True if content could be uploaded. False otherwise.
318
     */
319
    final public function can_upload(): bool {
320
        if (!$this->is_feature_supported(self::CAN_UPLOAD)) {
321
            return false;
322
        }
323
        if (!$this->can_access()) {
324
            return false;
325
        }
326
 
327
        $classname = 'contenttype/'.$this->get_plugin_name();
328
        $uploadcap = $classname.':upload';
329
        $hascapabilities = has_capability('moodle/contentbank:upload', $this->context)
330
            && has_capability($uploadcap, $this->context);
331
        return $hascapabilities && $this->is_upload_allowed();
332
    }
333
 
334
    /**
335
     * Returns plugin allows uploading.
336
     *
337
     * @return bool     True if plugin allows uploading. False otherwise.
338
     */
339
    protected function is_upload_allowed(): bool {
340
        // Plugins can overwrite this function to add any check they need.
341
        return true;
342
    }
343
 
344
    /**
345
     * Check if the user can delete this content.
346
     *
347
     * @param  content $content The content to be deleted.
348
     * @return bool True if content could be uploaded. False otherwise.
349
     */
350
    final public function can_delete(content $content): bool {
351
        global $USER;
352
 
353
        if ($this->context->id != $content->get_content()->contextid) {
354
            // The content has to have exactly the same context as this contenttype.
355
            return false;
356
        }
357
 
358
        $hascapability = has_capability('moodle/contentbank:deleteanycontent', $this->context);
359
        if ($content->get_content()->usercreated == $USER->id) {
360
            // This content has been created by the current user; check if she can delete her content.
361
            $hascapability = $hascapability || has_capability('moodle/contentbank:deleteowncontent', $this->context);
362
        }
363
 
364
        return $hascapability && $this->is_delete_allowed($content);
365
    }
366
 
367
    /**
368
     * Returns if content allows deleting.
369
     *
370
     * @param  content $content The content to be deleted.
371
     * @return bool True if content allows uploading. False otherwise.
372
     */
373
    protected function is_delete_allowed(content $content): bool {
374
        // Plugins can overwrite this function to add any check they need.
375
        return true;
376
    }
377
 
378
    /**
379
     * Check if the user can managed this content.
380
     *
381
     * @param  content $content The content to be managed.
382
     * @return bool     True if content could be managed. False otherwise.
383
     */
384
    final public function can_manage(content $content): bool {
385
        global $USER;
386
 
387
        if ($this->context->id != $content->get_content()->contextid) {
388
            // The content has to have exactly the same context as this contenttype.
389
            return false;
390
        }
391
 
392
        // Check main contentbank management permission.
393
        $hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
394
        if ($content->get_content()->usercreated == $USER->id) {
395
            // This content has been created by the current user; check if they can manage their content.
396
            $hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
397
        }
398
 
399
        return $hascapability && $this->is_manage_allowed($content);
400
    }
401
 
402
    /**
403
     * Returns if content allows managing.
404
     *
405
     * @param  content $content The content to be managed.
406
     * @return bool True if content allows uploading. False otherwise.
407
     */
408
    protected function is_manage_allowed(content $content): bool {
409
        // Plugins can overwrite this function to add any check they need.
410
        return true;
411
    }
412
 
413
    /**
414
     * Returns whether or not the user has permission to use the editor.
415
     * This function will be called with the content to be edited as parameter,
416
     * or null when is checking permission to create a new content using the editor.
417
     *
418
     * @param  content $content The content to be edited or null when creating a new content.
419
     * @return bool     True if the user can edit content. False otherwise.
420
     */
421
    final public function can_edit(?content $content = null): bool {
422
        if (!$this->is_feature_supported(self::CAN_EDIT)) {
423
            return false;
424
        }
425
 
426
        if (!$this->can_access()) {
427
            return false;
428
        }
429
 
430
        if (!is_null($content) && !$this->can_manage($content)) {
431
            return false;
432
        }
433
 
434
        $classname = 'contenttype/'.$this->get_plugin_name();
435
 
436
        $editioncap = $classname.':useeditor';
437
        $hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
438
        return $hascapabilities && $this->is_edit_allowed($content);
439
    }
440
 
441
    /**
442
     * Returns plugin allows edition.
443
     *
444
     * @param  content $content The content to be edited.
445
     * @return bool     True if plugin allows edition. False otherwise.
446
     */
447
    protected function is_edit_allowed(?content $content): bool {
448
        // Plugins can overwrite this function to add any check they need.
449
        return true;
450
    }
451
 
452
    /**
453
     * Returns whether or not the user has permission to download the content.
454
     *
455
     * @since  Moodle 3.10
456
     * @param  content $content The content to be downloaded.
457
     * @return bool    True if the user can download the content. False otherwise.
458
     */
459
    final public function can_download(content $content): bool {
460
        if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
461
            return false;
462
        }
463
 
464
        if (!$this->can_access()) {
465
            return false;
466
        }
467
 
468
        $hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
469
        return $hascapability && $this->is_download_allowed($content);
470
    }
471
 
472
    /**
473
     * Returns whether or not the user has permission to copy the content.
474
     *
475
     * @since  Moodle 4.3
476
     * @param  content $content The content to be copied.
477
     * @return bool    True if the user can copy the content. False otherwise.
478
     */
479
    final public function can_copy(content $content): bool {
480
        global $USER;
481
 
482
        if (!$this->is_feature_supported(self::CAN_COPY)) {
483
            return false;
484
        }
485
 
486
        if (!$this->can_access()) {
487
            return false;
488
        }
489
 
490
        if (!$this->is_copy_allowed($content)) {
491
            return false;
492
        }
493
 
494
        $hascapability = has_capability('moodle/contentbank:copyanycontent', $this->context);
495
        if (!$hascapability && ($content->get_content()->usercreated == $USER->id)) {
496
            $hascapability = has_capability('moodle/contentbank:copycontent', $this->context);
497
        }
498
        return $hascapability;
499
    }
500
 
501
    /**
502
     * Returns plugin allows downloading.
503
     *
504
     * @since  Moodle 3.10
505
     * @param  content $content The content to be downloaed.
506
     * @return bool    True if plugin allows downloading. False otherwise.
507
     */
508
    protected function is_download_allowed(content $content): bool {
509
        // Plugins can overwrite this function to add any check they need.
510
        return true;
511
    }
512
 
513
    /**
514
     * Returns plugin allows copying.
515
     *
516
     * @since  Moodle 4.3
517
     * @param  content $content The content to be copied.
518
     * @return bool    True if plugin allows copying. False otherwise.
519
     */
520
    protected function is_copy_allowed(content $content): bool {
521
        // Plugins can overwrite this function to add any check they need.
522
        return true;
523
    }
524
 
525
    /**
526
     * Returns the plugin supports the feature.
527
     *
528
     * @param string $feature Feature code e.g CAN_UPLOAD
529
     * @return bool     True if content could be uploaded. False otherwise.
530
     */
531
    final public function is_feature_supported(string $feature): bool {
532
        return in_array($feature, $this->get_implemented_features());
533
    }
534
 
535
    /**
536
     * Return an array of implemented features by the plugins.
537
     *
538
     * @return array
539
     */
540
    abstract protected function get_implemented_features(): array;
541
 
542
    /**
543
     * Return an array of extensions the plugins could manage.
544
     *
545
     * @return array
546
     */
547
    abstract public function get_manageable_extensions(): array;
548
 
549
    /**
550
     * Returns the list of different types of the given content type.
551
     *
552
     * A content type can have one or more options for creating content. This method will report all of them or only the content
553
     * type itself if it has no other options.
554
     *
555
     * @return array An object for each type:
556
     *     - string typename: descriptive name of the type.
557
     *     - string typeeditorparams: params required by this content type editor.
558
     *     - url typeicon: this type icon.
559
     */
560
    abstract public function get_contenttype_types(): array;
561
}