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
 
18
/**
19
 * Definition of a class stored_file.
20
 *
21
 * @package   core_files
22
 * @copyright 2008 Petr Skoda {@link http://skodak.org}
23
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
use Psr\Http\Message\StreamInterface;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
require_once($CFG->dirroot . '/lib/filestorage/file_progress.php');
31
require_once($CFG->dirroot . '/lib/filestorage/file_system.php');
32
 
33
/**
34
 * Class representing local files stored in a sha1 file pool.
35
 *
36
 * Since Moodle 2.0 file contents are stored in sha1 pool and
37
 * all other file information is stored in new "files" database table.
38
 *
39
 * @package   core_files
40
 * @category  files
41
 * @copyright 2008 Petr Skoda {@link http://skodak.org}
42
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 * @since     Moodle 2.0
44
 */
45
class stored_file {
46
    /** @var file_storage file storage pool instance */
47
    private $fs;
48
    /** @var stdClass record from the files table left join files_reference table */
49
    private $file_record;
50
    /** @var repository repository plugin instance */
51
    private $repository;
52
    /** @var file_system filesystem instance */
53
    private $filesystem;
54
 
55
    /**
56
     * @var int Indicates a file handle of the type returned by fopen.
57
     */
58
    const FILE_HANDLE_FOPEN = 0;
59
 
60
    /**
61
     * @var int Indicates a file handle of the type returned by gzopen.
62
     */
63
    const FILE_HANDLE_GZOPEN = 1;
64
 
65
 
66
    /**
67
     * Constructor, this constructor should be called ONLY from the file_storage class!
68
     *
69
     * @param file_storage $fs file  storage instance
70
     * @param stdClass $file_record description of file
71
     * @param string $deprecated
72
     */
73
    public function __construct(file_storage $fs, stdClass $file_record, $deprecated = null) {
74
        global $DB, $CFG;
75
        $this->fs          = $fs;
76
        $this->file_record = clone($file_record); // prevent modifications
77
 
78
        if (!empty($file_record->repositoryid)) {
79
            require_once("$CFG->dirroot/repository/lib.php");
80
            $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
81
            if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
82
                // Repository cannot do file reference.
83
                throw new moodle_exception('error');
84
            }
85
        } else {
86
            $this->repository = null;
87
        }
88
        // make sure all reference fields exist in file_record even when it is not a reference
89
        foreach (array('referencelastsync', 'referencefileid', 'reference', 'repositoryid') as $key) {
90
            if (empty($this->file_record->$key)) {
91
                $this->file_record->$key = null;
92
            }
93
        }
94
 
95
        $this->filesystem = $fs->get_file_system();
96
    }
97
 
98
    /**
99
     * Magic method, called during serialization.
100
     *
101
     * @return array
102
     */
103
    public function __sleep() {
104
        // We only ever want the file_record saved, not the file_storage object.
105
        return ['file_record'];
106
    }
107
 
108
    /**
109
     * Magic method, called during unserialization.
110
     */
111
    public function __wakeup() {
112
        // Recreate our stored_file based on the file_record, and using file storage retrieved the correct way.
113
        $this->__construct(get_file_storage(), $this->file_record);
114
    }
115
 
116
    /**
117
     * Whether or not this is a external resource
118
     *
119
     * @return bool
120
     */
121
    public function is_external_file() {
122
        return !empty($this->repository);
123
    }
124
 
125
    /**
126
     * Whether or not this is a controlled link. Note that repositories cannot support FILE_REFERENCE and FILE_CONTROLLED_LINK.
127
     *
128
     * @return bool
129
     */
130
    public function is_controlled_link() {
131
        return $this->is_external_file() && $this->repository->supported_returntypes() & FILE_CONTROLLED_LINK;
132
    }
133
 
134
    /**
135
     * Update some file record fields
136
     * NOTE: Must remain protected
137
     *
138
     * @param stdClass $dataobject
139
     */
140
    protected function update($dataobject) {
141
        global $DB;
142
        $updatereferencesneeded = false;
143
        $updatemimetype = false;
144
        $keys = array_keys((array)$this->file_record);
145
        $filepreupdate = clone($this->file_record);
146
        foreach ($dataobject as $field => $value) {
147
            if (in_array($field, $keys)) {
148
                if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
149
                    throw new file_exception('storedfileproblem', 'Invalid contextid');
150
                }
151
 
152
                if ($field == 'component') {
153
                    $value = clean_param($value, PARAM_COMPONENT);
154
                    if (empty($value)) {
155
                        throw new file_exception('storedfileproblem', 'Invalid component');
156
                    }
157
                }
158
 
159
                if ($field == 'filearea') {
160
                    $value = clean_param($value, PARAM_AREA);
161
                    if (empty($value)) {
162
                        throw new file_exception('storedfileproblem', 'Invalid filearea');
163
                    }
164
                }
165
 
166
                if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
167
                    throw new file_exception('storedfileproblem', 'Invalid itemid');
168
                }
169
 
170
 
171
                if ($field == 'filepath') {
172
                    $value = clean_param($value, PARAM_PATH);
173
                    if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
174
                        // path must start and end with '/'
175
                        throw new file_exception('storedfileproblem', 'Invalid file path');
176
                    }
177
                }
178
 
179
                if ($field == 'filename') {
180
                    // folder has filename == '.', so we pass this
181
                    if ($value != '.') {
182
                        $value = clean_param($value, PARAM_FILE);
183
                    }
184
                    if ($value === '') {
185
                        throw new file_exception('storedfileproblem', 'Invalid file name');
186
                    }
187
                }
188
 
189
                if ($field === 'timecreated' or $field === 'timemodified') {
190
                    if (!is_number($value)) {
191
                        throw new file_exception('storedfileproblem', 'Invalid timestamp');
192
                    }
193
                    if ($value < 0) {
194
                        $value = 0;
195
                    }
196
                }
197
 
198
                if ($field === 'referencefileid') {
199
                    if (!is_null($value) and !is_number($value)) {
200
                        throw new file_exception('storedfileproblem', 'Invalid reference info');
201
                    }
202
                }
203
 
204
                if (($field == 'contenthash' || $field == 'filesize') && $this->file_record->$field != $value) {
205
                    $updatereferencesneeded = true;
206
                }
207
 
208
                if ($updatereferencesneeded || ($field === 'filename' && $this->file_record->filename != $value)) {
209
                    $updatemimetype = true;
210
                }
211
 
212
                // adding the field
213
                $this->file_record->$field = $value;
214
            } else {
215
                throw new coding_exception("Invalid field name, $field doesn't exist in file record");
216
            }
217
        }
218
        // Validate mimetype field
219
        if ($updatemimetype) {
220
            $mimetype = $this->filesystem->mimetype_from_storedfile($this);
221
            $this->file_record->mimetype = $mimetype;
222
        }
223
 
224
        $DB->update_record('files', $this->file_record);
225
        if ($updatereferencesneeded) {
226
            // Either filesize or contenthash of this file have changed. Update all files that reference to it.
227
            $this->fs->update_references_to_storedfile($this);
228
        }
229
 
230
        // Callback for file update.
231
        if (!$this->is_directory()) {
232
            if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
233
                foreach ($pluginsfunction as $plugintype => $plugins) {
234
                    foreach ($plugins as $pluginfunction) {
235
                        $pluginfunction($this->file_record, $filepreupdate);
236
                    }
237
                }
238
            }
239
        }
240
    }
241
 
242
    /**
243
     * Rename filename
244
     *
245
     * @param string $filepath file path
246
     * @param string $filename file name
247
     */
248
    public function rename($filepath, $filename) {
249
        if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
250
            $a = new stdClass();
251
            $a->contextid = $this->get_contextid();
252
            $a->component = $this->get_component();
253
            $a->filearea  = $this->get_filearea();
254
            $a->itemid    = $this->get_itemid();
255
            $a->filepath  = $filepath;
256
            $a->filename  = $filename;
257
            throw new file_exception('storedfilenotcreated', $a, 'file exists, cannot rename');
258
        }
259
        $filerecord = new stdClass;
260
        $filerecord->filepath = $filepath;
261
        $filerecord->filename = $filename;
262
        // populate the pathname hash
263
        $filerecord->pathnamehash = $this->fs->get_pathname_hash($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath, $filename);
264
        $this->update($filerecord);
265
    }
266
 
267
    /**
268
     * Replaces the fields that might have changed when file was overriden in filepicker:
269
     * reference, contenthash, filesize, userid
270
     *
271
     * Note that field 'source' must be updated separately because
272
     * it has different format for draft and non-draft areas and
273
     * this function will usually be used to replace non-draft area
274
     * file with draft area file.
275
     *
276
     * @param stored_file $newfile
277
     * @throws coding_exception
278
     */
279
    public function replace_file_with(stored_file $newfile) {
280
        if ($newfile->get_referencefileid() &&
281
                $this->fs->get_references_count_by_storedfile($this)) {
282
            // The new file is a reference.
283
            // The current file has other local files referencing to it.
284
            // Double reference is not allowed.
285
            throw new moodle_exception('errordoublereference', 'repository');
286
        }
287
 
288
        $filerecord = new stdClass;
289
        if ($this->filesystem->is_file_readable_remotely_by_storedfile($newfile)) {
290
            $contenthash = $newfile->get_contenthash();
291
            $filerecord->contenthash = $contenthash;
292
        } else {
293
            throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
294
        }
295
        $filerecord->filesize = $newfile->get_filesize();
296
        $filerecord->referencefileid = $newfile->get_referencefileid();
297
        $filerecord->userid = $newfile->get_userid();
298
        $oldcontenthash = $this->get_contenthash();
299
        $this->update($filerecord);
300
        $this->filesystem->remove_file($oldcontenthash);
301
    }
302
 
303
    /**
304
     * Unlink the stored file from the referenced file
305
     *
306
     * This methods destroys the link to the record in files_reference table. This effectively
307
     * turns the stored file from being an alias to a plain copy. However, the caller has
308
     * to make sure that the actual file's content has beed synced prior to calling this method.
309
     */
310
    public function delete_reference() {
311
        global $DB;
312
 
313
        if (!$this->is_external_file()) {
314
            throw new coding_exception('An attempt to unlink a non-reference file.');
315
        }
316
 
317
        $transaction = $DB->start_delegated_transaction();
318
 
319
        // Are we the only one referring to the original file? If so, delete the
320
        // referenced file record. Note we do not use file_storage::search_references_count()
321
        // here because we want to count draft files too and we are at a bit lower access level here.
322
        $countlinks = $DB->count_records('files',
323
            array('referencefileid' => $this->file_record->referencefileid));
324
        if ($countlinks == 1) {
325
            $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
326
        }
327
 
328
        // Update the underlying record in the database.
329
        $update = new stdClass();
330
        $update->referencefileid = null;
331
        $this->update($update);
332
 
333
        $transaction->allow_commit();
334
 
335
        // Update our properties and the record in the memory.
336
        $this->repository = null;
337
        $this->file_record->repositoryid = null;
338
        $this->file_record->reference = null;
339
        $this->file_record->referencefileid = null;
340
        $this->file_record->referencelastsync = null;
341
    }
342
 
343
    /**
344
     * Is this a directory?
345
     *
346
     * Directories are only emulated, internally they are stored as empty
347
     * files with a "." instead of name - this means empty directory contains
348
     * exactly one empty file with name dot.
349
     *
350
     * @return bool true means directory, false means file
351
     */
352
    public function is_directory() {
353
        return ($this->file_record->filename === '.');
354
    }
355
 
356
    /**
357
     * Delete file from files table.
358
     *
359
     * The content of files stored in sha1 pool is reclaimed
360
     * later - the occupied disk space is reclaimed much later.
361
     *
362
     * @return bool always true or exception if error occurred
363
     */
364
    public function delete() {
365
        global $DB;
366
 
367
        if ($this->is_directory()) {
368
            // Directories can not be referenced, just delete the record.
369
            $DB->delete_records('files', array('id'=>$this->file_record->id));
370
 
371
        } else {
372
            $transaction = $DB->start_delegated_transaction();
373
 
374
            // If there are other files referring to this file, convert them to copies.
375
            if ($files = $this->fs->get_references_by_storedfile($this)) {
376
                foreach ($files as $file) {
377
                    $this->fs->import_external_file($file);
378
                }
379
            }
380
 
381
            // If this file is a reference (alias) to another file, unlink it first.
382
            if ($this->is_external_file()) {
383
                $this->delete_reference();
384
            }
385
 
386
            // Now delete the file record.
387
            $DB->delete_records('files', array('id'=>$this->file_record->id));
388
 
389
            $transaction->allow_commit();
390
 
391
            if (!$this->is_directory()) {
392
                // Callback for file deletion.
393
                if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
394
                    foreach ($pluginsfunction as $plugintype => $plugins) {
395
                        foreach ($plugins as $pluginfunction) {
396
                            $pluginfunction($this->file_record);
397
                        }
398
                    }
399
                }
400
            }
401
        }
402
 
403
        // Move pool file to trash if content not needed any more.
404
        $this->filesystem->remove_file($this->file_record->contenthash);
405
        return true; // BC only
406
    }
407
 
408
    /**
409
    * adds this file path to a curl request (POST only)
410
    *
411
    * @param curl $curlrequest the curl request object
412
    * @param string $key what key to use in the POST request
413
    * @return void
414
    */
415
    public function add_to_curl_request(&$curlrequest, $key) {
416
        return $this->filesystem->add_to_curl_request($this, $curlrequest, $key);
417
    }
418
 
419
    /**
420
     * Returns file handle - read only mode, no writing allowed into pool files!
421
     *
422
     * When you want to modify a file, create a new file and delete the old one.
423
     *
424
     * @param int $type Type of file handle (FILE_HANDLE_xx constant)
425
     * @return resource file handle
426
     */
427
    public function get_content_file_handle($type = self::FILE_HANDLE_FOPEN) {
428
        return $this->filesystem->get_content_file_handle($this, $type);
429
    }
430
 
431
    /**
432
     * Get a read-only PSR-7 stream for this file.
433
     *
434
     * Note: This stream is read-only. If you want to modify the file, create a new file and delete the old one.
435
     * The File API creates immutable files.
436
     *
437
     * @return StreamInterface
438
     */
439
    public function get_psr_stream(): StreamInterface {
440
        return $this->filesystem->get_psr_stream($this);
441
    }
442
 
443
    /**
444
     * Dumps file content to page.
445
     */
446
    public function readfile() {
447
        return $this->filesystem->readfile($this);
448
    }
449
 
450
    /**
451
     * Returns file content as string.
452
     *
453
     * @return string content
454
     */
455
    public function get_content() {
456
        return $this->filesystem->get_content($this);
457
    }
458
 
459
    /**
460
     * Copy content of file to given pathname.
461
     *
462
     * @param string $pathname real path to the new file
463
     * @return bool success
464
     */
465
    public function copy_content_to($pathname) {
466
        return $this->filesystem->copy_content_from_storedfile($this, $pathname);
467
    }
468
 
469
    /**
470
     * Copy content of file to temporary folder and returns file path
471
     *
472
     * @param string $dir name of the temporary directory
473
     * @param string $fileprefix prefix of temporary file.
474
     * @return string|bool path of temporary file or false.
475
     */
476
    public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
477
        $tempfile = false;
478
        if (!$dir = make_temp_directory($dir)) {
479
            return false;
480
        }
481
        if (!$tempfile = tempnam($dir, $fileprefix)) {
482
            return false;
483
        }
484
        if (!$this->copy_content_to($tempfile)) {
485
            // something went wrong
486
            @unlink($tempfile);
487
            return false;
488
        }
489
        return $tempfile;
490
    }
491
 
492
    /**
493
     * List contents of archive.
494
     *
495
     * @param file_packer $packer file packer instance
496
     * @return array of file infos
497
     */
498
    public function list_files(file_packer $packer) {
499
        return $this->filesystem->list_files($this, $packer);
500
    }
501
 
502
    /**
503
     * Returns the total size (in bytes) of the contents of an archive.
504
     *
505
     * @param file_packer $packer file packer instance
506
     * @return int|null total size in bytes
507
     */
508
    public function get_total_content_size(file_packer $packer): ?int {
509
        // Fetch the contents of the archive.
510
        $files = $this->list_files($packer);
511
 
512
        // Early return if the value of $files is not of type array.
513
        // This can happen when the utility class is unable to open or read the contents of the archive.
514
        if (!is_array($files)) {
515
            return null;
516
        }
517
 
518
        return array_reduce($files, function ($contentsize, $file) {
519
            return $contentsize + $file->size;
520
        }, 0);
521
    }
522
 
523
    /**
524
     * Extract file to given file path (real OS filesystem), existing files are overwritten.
525
     *
526
     * @param file_packer $packer file packer instance
527
     * @param string $pathname target directory
528
     * @param file_progress $progress Progress indicator callback or null if not required
529
     * @return array|bool list of processed files; false if error
530
     */
531
    public function extract_to_pathname(file_packer $packer, $pathname,
1441 ariadna 532
            ?file_progress $progress = null) {
1 efrain 533
        return $this->filesystem->extract_to_pathname($this, $packer, $pathname, $progress);
534
    }
535
 
536
    /**
537
     * Extract file to given file path (real OS filesystem), existing files are overwritten.
538
     *
539
     * @param file_packer $packer file packer instance
540
     * @param int $contextid context ID
541
     * @param string $component component
542
     * @param string $filearea file area
543
     * @param int $itemid item ID
544
     * @param string $pathbase path base
545
     * @param int $userid user ID
546
     * @param file_progress $progress Progress indicator callback or null if not required
547
     * @return array|bool list of processed files; false if error
548
     */
549
    public function extract_to_storage(file_packer $packer, $contextid,
1441 ariadna 550
            $component, $filearea, $itemid, $pathbase, $userid = null, ?file_progress $progress = null) {
1 efrain 551
 
552
        return $this->filesystem->extract_to_storage($this, $packer, $contextid, $component, $filearea,
553
                $itemid, $pathbase, $userid, $progress);
554
    }
555
 
556
    /**
557
     * Add file/directory into archive.
558
     *
559
     * @param file_archive $filearch file archive instance
560
     * @param string $archivepath pathname in archive
561
     * @return bool success
562
     */
563
    public function archive_file(file_archive $filearch, $archivepath) {
564
        if ($this->repository) {
565
            $this->sync_external_file();
566
            if ($this->compare_to_string('')) {
567
                // This file is not stored locally - attempt to retrieve it from the repository.
568
                // This may happen if the repository deliberately does not fetch files, or if there is a failure with the sync.
569
                $fileinfo = $this->repository->get_file($this->get_reference());
570
                if (isset($fileinfo['path'])) {
571
                    return $filearch->add_file_from_pathname($archivepath, $fileinfo['path']);
572
                }
573
            }
574
        }
575
 
576
        return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
577
    }
578
 
579
    /**
580
     * Returns information about image,
581
     * information is determined from the file content
582
     *
583
     * @return mixed array with width, height and mimetype; false if not an image
584
     */
585
    public function get_imageinfo() {
586
        return $this->filesystem->get_imageinfo($this);
587
    }
588
 
589
    /**
590
     * Verifies the file is a valid web image - gif, png and jpeg only.
591
     *
592
     * It should be ok to serve this image from server without any other security workarounds.
593
     *
594
     * @return bool true if file ok
595
     */
596
    public function is_valid_image() {
597
        $mimetype = $this->get_mimetype();
598
        if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
599
            return false;
600
        }
601
        if (!$info = $this->get_imageinfo()) {
602
            return false;
603
        }
604
        if ($info['mimetype'] !== $mimetype) {
605
            return false;
606
        }
607
        // ok, GD likes this image
608
        return true;
609
    }
610
 
611
    /**
612
     * Returns parent directory, creates missing parents if needed.
613
     *
614
     * @return stored_file
615
     */
616
    public function get_parent_directory() {
617
        if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
618
            //root dir does not have parent
619
            return null;
620
        }
621
 
622
        if ($this->file_record->filename !== '.') {
623
            return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $this->file_record->filepath);
624
        }
625
 
626
        $filepath = $this->file_record->filepath;
627
        $filepath = trim($filepath, '/');
628
        $dirs = explode('/', $filepath);
629
        array_pop($dirs);
630
        $filepath = implode('/', $dirs);
631
        $filepath = ($filepath === '') ? '/' : "/$filepath/";
632
 
633
        return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
634
    }
635
 
636
    /**
637
     * Set synchronised content from file.
638
     *
639
     * @param string $path Path to the file.
640
     */
641
    public function set_synchronised_content_from_file($path) {
642
        $this->fs->synchronise_stored_file_from_file($this, $path, $this->file_record);
643
    }
644
 
645
    /**
646
     * Set synchronised content from content.
647
     *
648
     * @param string $content File content.
649
     */
650
    public function set_synchronised_content_from_string($content) {
651
        $this->fs->synchronise_stored_file_from_string($this, $content, $this->file_record);
652
    }
653
 
654
    /**
655
     * Synchronize file if it is a reference and needs synchronizing
656
     *
657
     * Updates contenthash and filesize
658
     */
659
    public function sync_external_file() {
660
        if (!empty($this->repository)) {
661
            $this->repository->sync_reference($this);
662
        }
663
    }
664
 
665
    /**
666
     * Returns context id of the file
667
     *
668
     * @return int context id
669
     */
670
    public function get_contextid() {
671
        return $this->file_record->contextid;
672
    }
673
 
674
    /**
675
     * Returns component name - this is the owner of the areas,
676
     * nothing else is allowed to read or modify the files directly!!
677
     *
678
     * @return string
679
     */
680
    public function get_component() {
681
        return $this->file_record->component;
682
    }
683
 
684
    /**
685
     * Returns file area name, this divides files of one component into groups with different access control.
686
     * All files in one area have the same access control.
687
     *
688
     * @return string
689
     */
690
    public function get_filearea() {
691
        return $this->file_record->filearea;
692
    }
693
 
694
    /**
695
     * Returns returns item id of file.
696
     *
697
     * @return int
698
     */
699
    public function get_itemid() {
700
        return $this->file_record->itemid;
701
    }
702
 
703
    /**
704
     * Returns file path - starts and ends with /, \ are not allowed.
705
     *
706
     * @return string
707
     */
708
    public function get_filepath() {
709
        return $this->file_record->filepath;
710
    }
711
 
712
    /**
713
     * Returns file name or '.' in case of directories.
714
     *
715
     * @return string
716
     */
717
    public function get_filename() {
718
        return $this->file_record->filename;
719
    }
720
 
721
    /**
722
     * Returns id of user who created the file.
723
     *
724
     * @return int
725
     */
726
    public function get_userid() {
727
        return $this->file_record->userid;
728
    }
729
 
730
    /**
731
     * Returns the size of file in bytes.
732
     *
733
     * @return int bytes
734
     */
735
    public function get_filesize() {
736
        $this->sync_external_file();
737
        return $this->file_record->filesize;
738
    }
739
 
740
     /**
741
     * Returns mime type of file.
742
     *
743
     * @return string
744
     */
745
    public function get_mimetype() {
746
        return $this->file_record->mimetype;
747
    }
748
 
749
    /**
750
     * Returns unix timestamp of file creation date.
751
     *
752
     * @return int
753
     */
754
    public function get_timecreated() {
755
        return $this->file_record->timecreated;
756
    }
757
 
758
    /**
759
     * Returns unix timestamp of last file modification.
760
     *
761
     * @return int
762
     */
763
    public function get_timemodified() {
764
        $this->sync_external_file();
765
        return $this->file_record->timemodified;
766
    }
767
 
768
    /**
769
     * set timemodified
770
     *
771
     * @param int $timemodified
772
     */
773
    public function set_timemodified($timemodified) {
774
        $filerecord = new stdClass;
775
        $filerecord->timemodified = $timemodified;
776
        $this->update($filerecord);
777
    }
778
 
779
    /**
780
     * Returns file status flag.
781
     *
782
     * @return int 0 means file OK, anything else is a problem and file can not be used
783
     */
784
    public function get_status() {
785
        return $this->file_record->status;
786
    }
787
 
788
    /**
789
     * Returns file id.
790
     *
791
     * @return int
792
     */
793
    public function get_id() {
794
        return $this->file_record->id;
795
    }
796
 
797
    /**
798
     * Returns sha1 hash of file content.
799
     *
800
     * @return string
801
     */
802
    public function get_contenthash() {
803
        $this->sync_external_file();
804
        return $this->file_record->contenthash;
805
    }
806
 
807
    /**
808
     * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
809
     *
810
     * @return string
811
     */
812
    public function get_pathnamehash() {
813
        return $this->file_record->pathnamehash;
814
    }
815
 
816
    /**
817
     * Returns the license type of the file, it is a short name referred from license table.
818
     *
819
     * @return string
820
     */
821
    public function get_license() {
822
        return $this->file_record->license;
823
    }
824
 
825
    /**
826
     * Set license
827
     *
828
     * @param string $license license
829
     */
830
    public function set_license($license) {
831
        $filerecord = new stdClass;
832
        $filerecord->license = $license;
833
        $this->update($filerecord);
834
    }
835
 
836
    /**
837
     * Returns the author name of the file.
838
     *
839
     * @return string
840
     */
841
    public function get_author() {
842
        return $this->file_record->author;
843
    }
844
 
845
    /**
846
     * Set author
847
     *
848
     * @param string $author
849
     */
850
    public function set_author($author) {
851
        $filerecord = new stdClass;
852
        $filerecord->author = $author;
853
        $this->update($filerecord);
854
    }
855
 
856
    /**
857
     * Returns the source of the file, usually it is a url.
858
     *
859
     * @return string
860
     */
861
    public function get_source() {
862
        return $this->file_record->source;
863
    }
864
 
865
    /**
866
     * Set license
867
     *
868
     * @param string $license license
869
     */
870
    public function set_source($source) {
871
        $filerecord = new stdClass;
872
        $filerecord->source = $source;
873
        $this->update($filerecord);
874
    }
875
 
876
 
877
    /**
878
     * Returns the sort order of file
879
     *
880
     * @return int
881
     */
882
    public function get_sortorder() {
883
        return $this->file_record->sortorder;
884
    }
885
 
886
    /**
887
     * Set file sort order
888
     *
889
     * @param int $sortorder
890
     * @return int
891
     */
892
    public function set_sortorder($sortorder) {
893
        $oldorder = $this->file_record->sortorder;
894
        $filerecord = new stdClass;
895
        $filerecord->sortorder = $sortorder;
896
        $this->update($filerecord);
897
        if (!$this->is_directory()) {
898
            // Callback for file sort order change.
899
            if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
900
                foreach ($pluginsfunction as $plugintype => $plugins) {
901
                    foreach ($plugins as $pluginfunction) {
902
                        $pluginfunction($this->file_record, $oldorder, $sortorder);
903
                    }
904
                }
905
            }
906
        }
907
    }
908
 
909
    /**
910
     * Returns repository id
911
     *
912
     * @return int|null
913
     */
914
    public function get_repository_id() {
915
        if (!empty($this->repository)) {
916
            return $this->repository->id;
917
        } else {
918
            return null;
919
        }
920
    }
921
 
922
    /**
923
     * Returns repository type.
924
     *
925
     * @return mixed str|null the repository type or null if is not an external file
926
     * @since  Moodle 3.3
927
     */
928
    public function get_repository_type() {
929
 
930
        if (!empty($this->repository)) {
931
            return $this->repository->get_typename();
932
        } else {
933
            return null;
934
        }
935
    }
936
 
937
 
938
    /**
939
     * get reference file id
940
     * @return int
941
     */
942
    public function get_referencefileid() {
943
        return $this->file_record->referencefileid;
944
    }
945
 
946
    /**
947
     * Get reference last sync time
948
     * @return int
949
     */
950
    public function get_referencelastsync() {
951
        return $this->file_record->referencelastsync;
952
    }
953
 
954
    /**
955
     * Returns file reference
956
     *
957
     * @return string
958
     */
959
    public function get_reference() {
960
        return $this->file_record->reference;
961
    }
962
 
963
    /**
964
     * Get human readable file reference information
965
     *
966
     * @return string
967
     */
968
    public function get_reference_details() {
969
        return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
970
    }
971
 
972
    /**
973
     * Called after reference-file has been synchronized with the repository
974
     *
975
     * We update contenthash, filesize and status in files table if changed
976
     * and we always update lastsync in files_reference table
977
     *
978
     * @param null|string $contenthash if set to null contenthash is not changed
979
     * @param int $filesize new size of the file
980
     * @param int $status new status of the file (0 means OK, 666 - source missing)
981
     * @param int $timemodified last time modified of the source, if known
982
     */
983
    public function set_synchronized($contenthash, $filesize, $status = 0, $timemodified = null) {
984
        if (!$this->is_external_file()) {
985
            return;
986
        }
987
        $now = time();
988
        if ($contenthash === null) {
989
            $contenthash = $this->file_record->contenthash;
990
        }
991
        if ($contenthash != $this->file_record->contenthash) {
992
            $oldcontenthash = $this->file_record->contenthash;
993
        }
994
        // this will update all entries in {files} that have the same filereference id
995
        $this->fs->update_references($this->file_record->referencefileid, $now, null, $contenthash, $filesize, $status, $timemodified);
996
        // we don't need to call update() for this object, just set the values of changed fields
997
        $this->file_record->contenthash = $contenthash;
998
        $this->file_record->filesize = $filesize;
999
        $this->file_record->status = $status;
1000
        $this->file_record->referencelastsync = $now;
1001
        if ($timemodified) {
1002
            $this->file_record->timemodified = $timemodified;
1003
        }
1004
        if (isset($oldcontenthash)) {
1005
            $this->filesystem->remove_file($oldcontenthash);
1006
        }
1007
    }
1008
 
1009
    /**
1010
     * Sets the error status for a file that could not be synchronised
1011
     */
1012
    public function set_missingsource() {
1013
        $this->set_synchronized($this->file_record->contenthash, $this->file_record->filesize, 666);
1014
    }
1015
 
1016
    /**
1017
     * Send file references
1018
     *
1019
     * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
1020
     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1021
     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1022
     * @param array $options additional options affecting the file serving
1023
     */
1024
    public function send_file($lifetime, $filter, $forcedownload, $options) {
1025
        $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
1026
    }
1027
 
1028
    /**
1029
     * Imports the contents of an external file into moodle filepool.
1030
     *
1031
     * @throws moodle_exception if file could not be downloaded or is too big
1032
     * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
1033
     */
1034
    public function import_external_file_contents($maxbytes = 0) {
1035
        if ($this->repository) {
1036
            $this->repository->import_external_file_contents($this, $maxbytes);
1037
        }
1038
    }
1039
 
1040
    /**
1041
     * Gets a file relative to this file in the repository and sends it to the browser.
1042
     * Checks the function repository::supports_relative_file() to make sure it can be used.
1043
     *
1044
     * @param string $relativepath the relative path to the file we are trying to access
1045
     */
1046
    public function send_relative_file($relativepath) {
1047
        if ($this->repository && $this->repository->supports_relative_file()) {
1048
            $relativepath = clean_param($relativepath, PARAM_PATH);
1049
            $this->repository->send_relative_file($this, $relativepath);
1050
        } else {
1051
            send_file_not_found();
1052
        }
1053
    }
1054
 
1055
    /**
1056
     * Generates a thumbnail for this stored_file.
1057
     *
1058
     * If the GD library has at least version 2 and PNG support is available, the returned data
1059
     * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
1060
     * returns contents of a JPEG file with black background containing the thumbnail.
1061
     *
1062
     * @param   int $width the width of the requested thumbnail
1063
     * @param   int $height the height of the requested thumbnail
1064
     * @return  string|bool false if a problem occurs, the thumbnail image data otherwise
1065
     */
1066
    public function generate_image_thumbnail($width, $height) {
1067
        global $CFG;
1068
        require_once($CFG->libdir . '/gdlib.php');
1069
 
1070
        if (empty($width) or empty($height)) {
1071
            return false;
1072
        }
1073
 
1074
        $content = $this->get_content();
1075
 
1076
        // Fetch the image information for this image.
1077
        $imageinfo = @getimagesizefromstring($content);
1078
        if (empty($imageinfo)) {
1079
            return false;
1080
        }
1081
 
1082
        // Create a new image from the file.
1083
        $original = @imagecreatefromstring($content);
1084
 
1085
        // Generate the thumbnail.
1086
        return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
1087
    }
1088
 
1089
    /**
1090
     * Generate a resized image for this stored_file.
1091
     *
1092
     * @param int|null $width The desired width, or null to only use the height.
1093
     * @param int|null $height The desired height, or null to only use the width.
1094
     * @return string|false False when a problem occurs, else the image data.
1095
     */
1096
    public function resize_image($width, $height) {
1097
        global $CFG;
1098
        require_once($CFG->libdir . '/gdlib.php');
1099
 
1100
        $content = $this->get_content();
1101
 
1102
        // Fetch the image information for this image.
1103
        $imageinfo = @getimagesizefromstring($content);
1104
        if (empty($imageinfo)) {
1105
            return false;
1106
        }
1107
 
1108
        // Create a new image from the file.
1109
        $original = @imagecreatefromstring($content);
1110
        if (empty($original)) {
1111
            return false;
1112
        }
1113
 
1114
        // Generate the resized image.
1115
        return resize_image_from_image($original, $imageinfo, $width, $height);
1116
    }
1117
 
1118
    /**
1119
     * Check whether the supplied file is the same as this file.
1120
     *
1121
     * @param   string $path The path to the file on disk
1122
     * @return  boolean
1123
     */
1124
    public function compare_to_path($path) {
1125
        return $this->get_contenthash() === file_storage::hash_from_path($path);
1126
    }
1127
 
1128
    /**
1129
     * Check whether the supplied content is the same as this file.
1130
     *
1131
     * @param   string $content The file content
1132
     * @return  boolean
1133
     */
1134
    public function compare_to_string($content) {
1135
        return $this->get_contenthash() === file_storage::hash_from_string($content);
1136
    }
1137
 
1138
    /**
1139
     * Generate a rotated image for this stored_file based on exif information.
1140
     *
1141
     * @return array|false False when a problem occurs, else the image data and image size.
1142
     * @since Moodle 3.8
1143
     */
1144
    public function rotate_image() {
1145
        $content = $this->get_content();
1146
        $mimetype = $this->get_mimetype();
1147
 
1148
        if ($mimetype === "image/jpeg" && function_exists("exif_read_data")) {
1149
            $exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($content));
1150
            if (isset($exif['ExifImageWidth']) && isset($exif['ExifImageLength']) && isset($exif['Orientation'])) {
1151
                $rotation = [
1152
                    3 => -180,
1153
                    6 => -90,
1154
                    8 => -270,
1155
                ];
1156
                $orientation = $exif['Orientation'];
1157
                if ($orientation !== 1) {
1158
                    $source = @imagecreatefromstring($content);
1159
                    $data = @imagerotate($source, $rotation[$orientation], 0);
1160
                    if (!empty($data)) {
1161
                        if ($orientation == 1 || $orientation == 3) {
1162
                            $size = [
1163
                                'width' => $exif["ExifImageWidth"],
1164
                                'height' => $exif["ExifImageLength"],
1165
                            ];
1166
                        } else {
1167
                            $size = [
1168
                                'height' => $exif["ExifImageWidth"],
1169
                                'width' => $exif["ExifImageLength"],
1170
                            ];
1171
                        }
1172
                        imagedestroy($source);
1173
                        return [$data, $size];
1174
                    }
1175
                }
1176
            }
1177
        }
1178
        return [false, false];
1179
    }
1180
}