Proyectos de Subversion Moodle

Rev

| 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
     * Function stored_file::replace_content_with() is deprecated. Please use stored_file::replace_file_with()
269
     *
270
     * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
271
     * @see stored_file::replace_file_with()
272
     */
273
    public function replace_content_with(stored_file $storedfile) {
274
        throw new coding_exception('Function stored_file::replace_content_with() can not be used any more . ' .
275
            'Please use stored_file::replace_file_with()');
276
    }
277
 
278
    /**
279
     * Replaces the fields that might have changed when file was overriden in filepicker:
280
     * reference, contenthash, filesize, userid
281
     *
282
     * Note that field 'source' must be updated separately because
283
     * it has different format for draft and non-draft areas and
284
     * this function will usually be used to replace non-draft area
285
     * file with draft area file.
286
     *
287
     * @param stored_file $newfile
288
     * @throws coding_exception
289
     */
290
    public function replace_file_with(stored_file $newfile) {
291
        if ($newfile->get_referencefileid() &&
292
                $this->fs->get_references_count_by_storedfile($this)) {
293
            // The new file is a reference.
294
            // The current file has other local files referencing to it.
295
            // Double reference is not allowed.
296
            throw new moodle_exception('errordoublereference', 'repository');
297
        }
298
 
299
        $filerecord = new stdClass;
300
        if ($this->filesystem->is_file_readable_remotely_by_storedfile($newfile)) {
301
            $contenthash = $newfile->get_contenthash();
302
            $filerecord->contenthash = $contenthash;
303
        } else {
304
            throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
305
        }
306
        $filerecord->filesize = $newfile->get_filesize();
307
        $filerecord->referencefileid = $newfile->get_referencefileid();
308
        $filerecord->userid = $newfile->get_userid();
309
        $oldcontenthash = $this->get_contenthash();
310
        $this->update($filerecord);
311
        $this->filesystem->remove_file($oldcontenthash);
312
    }
313
 
314
    /**
315
     * Unlink the stored file from the referenced file
316
     *
317
     * This methods destroys the link to the record in files_reference table. This effectively
318
     * turns the stored file from being an alias to a plain copy. However, the caller has
319
     * to make sure that the actual file's content has beed synced prior to calling this method.
320
     */
321
    public function delete_reference() {
322
        global $DB;
323
 
324
        if (!$this->is_external_file()) {
325
            throw new coding_exception('An attempt to unlink a non-reference file.');
326
        }
327
 
328
        $transaction = $DB->start_delegated_transaction();
329
 
330
        // Are we the only one referring to the original file? If so, delete the
331
        // referenced file record. Note we do not use file_storage::search_references_count()
332
        // here because we want to count draft files too and we are at a bit lower access level here.
333
        $countlinks = $DB->count_records('files',
334
            array('referencefileid' => $this->file_record->referencefileid));
335
        if ($countlinks == 1) {
336
            $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
337
        }
338
 
339
        // Update the underlying record in the database.
340
        $update = new stdClass();
341
        $update->referencefileid = null;
342
        $this->update($update);
343
 
344
        $transaction->allow_commit();
345
 
346
        // Update our properties and the record in the memory.
347
        $this->repository = null;
348
        $this->file_record->repositoryid = null;
349
        $this->file_record->reference = null;
350
        $this->file_record->referencefileid = null;
351
        $this->file_record->referencelastsync = null;
352
    }
353
 
354
    /**
355
     * Is this a directory?
356
     *
357
     * Directories are only emulated, internally they are stored as empty
358
     * files with a "." instead of name - this means empty directory contains
359
     * exactly one empty file with name dot.
360
     *
361
     * @return bool true means directory, false means file
362
     */
363
    public function is_directory() {
364
        return ($this->file_record->filename === '.');
365
    }
366
 
367
    /**
368
     * Delete file from files table.
369
     *
370
     * The content of files stored in sha1 pool is reclaimed
371
     * later - the occupied disk space is reclaimed much later.
372
     *
373
     * @return bool always true or exception if error occurred
374
     */
375
    public function delete() {
376
        global $DB;
377
 
378
        if ($this->is_directory()) {
379
            // Directories can not be referenced, just delete the record.
380
            $DB->delete_records('files', array('id'=>$this->file_record->id));
381
 
382
        } else {
383
            $transaction = $DB->start_delegated_transaction();
384
 
385
            // If there are other files referring to this file, convert them to copies.
386
            if ($files = $this->fs->get_references_by_storedfile($this)) {
387
                foreach ($files as $file) {
388
                    $this->fs->import_external_file($file);
389
                }
390
            }
391
 
392
            // If this file is a reference (alias) to another file, unlink it first.
393
            if ($this->is_external_file()) {
394
                $this->delete_reference();
395
            }
396
 
397
            // Now delete the file record.
398
            $DB->delete_records('files', array('id'=>$this->file_record->id));
399
 
400
            $transaction->allow_commit();
401
 
402
            if (!$this->is_directory()) {
403
                // Callback for file deletion.
404
                if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
405
                    foreach ($pluginsfunction as $plugintype => $plugins) {
406
                        foreach ($plugins as $pluginfunction) {
407
                            $pluginfunction($this->file_record);
408
                        }
409
                    }
410
                }
411
            }
412
        }
413
 
414
        // Move pool file to trash if content not needed any more.
415
        $this->filesystem->remove_file($this->file_record->contenthash);
416
        return true; // BC only
417
    }
418
 
419
    /**
420
    * adds this file path to a curl request (POST only)
421
    *
422
    * @param curl $curlrequest the curl request object
423
    * @param string $key what key to use in the POST request
424
    * @return void
425
    */
426
    public function add_to_curl_request(&$curlrequest, $key) {
427
        return $this->filesystem->add_to_curl_request($this, $curlrequest, $key);
428
    }
429
 
430
    /**
431
     * Returns file handle - read only mode, no writing allowed into pool files!
432
     *
433
     * When you want to modify a file, create a new file and delete the old one.
434
     *
435
     * @param int $type Type of file handle (FILE_HANDLE_xx constant)
436
     * @return resource file handle
437
     */
438
    public function get_content_file_handle($type = self::FILE_HANDLE_FOPEN) {
439
        return $this->filesystem->get_content_file_handle($this, $type);
440
    }
441
 
442
    /**
443
     * Get a read-only PSR-7 stream for this file.
444
     *
445
     * Note: This stream is read-only. If you want to modify the file, create a new file and delete the old one.
446
     * The File API creates immutable files.
447
     *
448
     * @return StreamInterface
449
     */
450
    public function get_psr_stream(): StreamInterface {
451
        return $this->filesystem->get_psr_stream($this);
452
    }
453
 
454
    /**
455
     * Dumps file content to page.
456
     */
457
    public function readfile() {
458
        return $this->filesystem->readfile($this);
459
    }
460
 
461
    /**
462
     * Returns file content as string.
463
     *
464
     * @return string content
465
     */
466
    public function get_content() {
467
        return $this->filesystem->get_content($this);
468
    }
469
 
470
    /**
471
     * Copy content of file to given pathname.
472
     *
473
     * @param string $pathname real path to the new file
474
     * @return bool success
475
     */
476
    public function copy_content_to($pathname) {
477
        return $this->filesystem->copy_content_from_storedfile($this, $pathname);
478
    }
479
 
480
    /**
481
     * Copy content of file to temporary folder and returns file path
482
     *
483
     * @param string $dir name of the temporary directory
484
     * @param string $fileprefix prefix of temporary file.
485
     * @return string|bool path of temporary file or false.
486
     */
487
    public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
488
        $tempfile = false;
489
        if (!$dir = make_temp_directory($dir)) {
490
            return false;
491
        }
492
        if (!$tempfile = tempnam($dir, $fileprefix)) {
493
            return false;
494
        }
495
        if (!$this->copy_content_to($tempfile)) {
496
            // something went wrong
497
            @unlink($tempfile);
498
            return false;
499
        }
500
        return $tempfile;
501
    }
502
 
503
    /**
504
     * List contents of archive.
505
     *
506
     * @param file_packer $packer file packer instance
507
     * @return array of file infos
508
     */
509
    public function list_files(file_packer $packer) {
510
        return $this->filesystem->list_files($this, $packer);
511
    }
512
 
513
    /**
514
     * Returns the total size (in bytes) of the contents of an archive.
515
     *
516
     * @param file_packer $packer file packer instance
517
     * @return int|null total size in bytes
518
     */
519
    public function get_total_content_size(file_packer $packer): ?int {
520
        // Fetch the contents of the archive.
521
        $files = $this->list_files($packer);
522
 
523
        // Early return if the value of $files is not of type array.
524
        // This can happen when the utility class is unable to open or read the contents of the archive.
525
        if (!is_array($files)) {
526
            return null;
527
        }
528
 
529
        return array_reduce($files, function ($contentsize, $file) {
530
            return $contentsize + $file->size;
531
        }, 0);
532
    }
533
 
534
    /**
535
     * Extract file to given file path (real OS filesystem), existing files are overwritten.
536
     *
537
     * @param file_packer $packer file packer instance
538
     * @param string $pathname target directory
539
     * @param file_progress $progress Progress indicator callback or null if not required
540
     * @return array|bool list of processed files; false if error
541
     */
542
    public function extract_to_pathname(file_packer $packer, $pathname,
543
            file_progress $progress = null) {
544
        return $this->filesystem->extract_to_pathname($this, $packer, $pathname, $progress);
545
    }
546
 
547
    /**
548
     * Extract file to given file path (real OS filesystem), existing files are overwritten.
549
     *
550
     * @param file_packer $packer file packer instance
551
     * @param int $contextid context ID
552
     * @param string $component component
553
     * @param string $filearea file area
554
     * @param int $itemid item ID
555
     * @param string $pathbase path base
556
     * @param int $userid user ID
557
     * @param file_progress $progress Progress indicator callback or null if not required
558
     * @return array|bool list of processed files; false if error
559
     */
560
    public function extract_to_storage(file_packer $packer, $contextid,
561
            $component, $filearea, $itemid, $pathbase, $userid = null, file_progress $progress = null) {
562
 
563
        return $this->filesystem->extract_to_storage($this, $packer, $contextid, $component, $filearea,
564
                $itemid, $pathbase, $userid, $progress);
565
    }
566
 
567
    /**
568
     * Add file/directory into archive.
569
     *
570
     * @param file_archive $filearch file archive instance
571
     * @param string $archivepath pathname in archive
572
     * @return bool success
573
     */
574
    public function archive_file(file_archive $filearch, $archivepath) {
575
        if ($this->repository) {
576
            $this->sync_external_file();
577
            if ($this->compare_to_string('')) {
578
                // This file is not stored locally - attempt to retrieve it from the repository.
579
                // This may happen if the repository deliberately does not fetch files, or if there is a failure with the sync.
580
                $fileinfo = $this->repository->get_file($this->get_reference());
581
                if (isset($fileinfo['path'])) {
582
                    return $filearch->add_file_from_pathname($archivepath, $fileinfo['path']);
583
                }
584
            }
585
        }
586
 
587
        return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
588
    }
589
 
590
    /**
591
     * Returns information about image,
592
     * information is determined from the file content
593
     *
594
     * @return mixed array with width, height and mimetype; false if not an image
595
     */
596
    public function get_imageinfo() {
597
        return $this->filesystem->get_imageinfo($this);
598
    }
599
 
600
    /**
601
     * Verifies the file is a valid web image - gif, png and jpeg only.
602
     *
603
     * It should be ok to serve this image from server without any other security workarounds.
604
     *
605
     * @return bool true if file ok
606
     */
607
    public function is_valid_image() {
608
        $mimetype = $this->get_mimetype();
609
        if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
610
            return false;
611
        }
612
        if (!$info = $this->get_imageinfo()) {
613
            return false;
614
        }
615
        if ($info['mimetype'] !== $mimetype) {
616
            return false;
617
        }
618
        // ok, GD likes this image
619
        return true;
620
    }
621
 
622
    /**
623
     * Returns parent directory, creates missing parents if needed.
624
     *
625
     * @return stored_file
626
     */
627
    public function get_parent_directory() {
628
        if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
629
            //root dir does not have parent
630
            return null;
631
        }
632
 
633
        if ($this->file_record->filename !== '.') {
634
            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);
635
        }
636
 
637
        $filepath = $this->file_record->filepath;
638
        $filepath = trim($filepath, '/');
639
        $dirs = explode('/', $filepath);
640
        array_pop($dirs);
641
        $filepath = implode('/', $dirs);
642
        $filepath = ($filepath === '') ? '/' : "/$filepath/";
643
 
644
        return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
645
    }
646
 
647
    /**
648
     * Set synchronised content from file.
649
     *
650
     * @param string $path Path to the file.
651
     */
652
    public function set_synchronised_content_from_file($path) {
653
        $this->fs->synchronise_stored_file_from_file($this, $path, $this->file_record);
654
    }
655
 
656
    /**
657
     * Set synchronised content from content.
658
     *
659
     * @param string $content File content.
660
     */
661
    public function set_synchronised_content_from_string($content) {
662
        $this->fs->synchronise_stored_file_from_string($this, $content, $this->file_record);
663
    }
664
 
665
    /**
666
     * Synchronize file if it is a reference and needs synchronizing
667
     *
668
     * Updates contenthash and filesize
669
     */
670
    public function sync_external_file() {
671
        if (!empty($this->repository)) {
672
            $this->repository->sync_reference($this);
673
        }
674
    }
675
 
676
    /**
677
     * Returns context id of the file
678
     *
679
     * @return int context id
680
     */
681
    public function get_contextid() {
682
        return $this->file_record->contextid;
683
    }
684
 
685
    /**
686
     * Returns component name - this is the owner of the areas,
687
     * nothing else is allowed to read or modify the files directly!!
688
     *
689
     * @return string
690
     */
691
    public function get_component() {
692
        return $this->file_record->component;
693
    }
694
 
695
    /**
696
     * Returns file area name, this divides files of one component into groups with different access control.
697
     * All files in one area have the same access control.
698
     *
699
     * @return string
700
     */
701
    public function get_filearea() {
702
        return $this->file_record->filearea;
703
    }
704
 
705
    /**
706
     * Returns returns item id of file.
707
     *
708
     * @return int
709
     */
710
    public function get_itemid() {
711
        return $this->file_record->itemid;
712
    }
713
 
714
    /**
715
     * Returns file path - starts and ends with /, \ are not allowed.
716
     *
717
     * @return string
718
     */
719
    public function get_filepath() {
720
        return $this->file_record->filepath;
721
    }
722
 
723
    /**
724
     * Returns file name or '.' in case of directories.
725
     *
726
     * @return string
727
     */
728
    public function get_filename() {
729
        return $this->file_record->filename;
730
    }
731
 
732
    /**
733
     * Returns id of user who created the file.
734
     *
735
     * @return int
736
     */
737
    public function get_userid() {
738
        return $this->file_record->userid;
739
    }
740
 
741
    /**
742
     * Returns the size of file in bytes.
743
     *
744
     * @return int bytes
745
     */
746
    public function get_filesize() {
747
        $this->sync_external_file();
748
        return $this->file_record->filesize;
749
    }
750
 
751
     /**
752
     * Function stored_file::set_filesize() is deprecated. Please use stored_file::replace_file_with
753
     *
754
     * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
755
     * @see stored_file::replace_file_with()
756
     */
757
    public function set_filesize($filesize) {
758
        throw new coding_exception('Function stored_file::set_filesize() can not be used any more. ' .
759
            'Please use stored_file::replace_file_with()');
760
    }
761
 
762
    /**
763
     * Returns mime type of file.
764
     *
765
     * @return string
766
     */
767
    public function get_mimetype() {
768
        return $this->file_record->mimetype;
769
    }
770
 
771
    /**
772
     * Returns unix timestamp of file creation date.
773
     *
774
     * @return int
775
     */
776
    public function get_timecreated() {
777
        return $this->file_record->timecreated;
778
    }
779
 
780
    /**
781
     * Returns unix timestamp of last file modification.
782
     *
783
     * @return int
784
     */
785
    public function get_timemodified() {
786
        $this->sync_external_file();
787
        return $this->file_record->timemodified;
788
    }
789
 
790
    /**
791
     * set timemodified
792
     *
793
     * @param int $timemodified
794
     */
795
    public function set_timemodified($timemodified) {
796
        $filerecord = new stdClass;
797
        $filerecord->timemodified = $timemodified;
798
        $this->update($filerecord);
799
    }
800
 
801
    /**
802
     * Returns file status flag.
803
     *
804
     * @return int 0 means file OK, anything else is a problem and file can not be used
805
     */
806
    public function get_status() {
807
        return $this->file_record->status;
808
    }
809
 
810
    /**
811
     * Returns file id.
812
     *
813
     * @return int
814
     */
815
    public function get_id() {
816
        return $this->file_record->id;
817
    }
818
 
819
    /**
820
     * Returns sha1 hash of file content.
821
     *
822
     * @return string
823
     */
824
    public function get_contenthash() {
825
        $this->sync_external_file();
826
        return $this->file_record->contenthash;
827
    }
828
 
829
    /**
830
     * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
831
     *
832
     * @return string
833
     */
834
    public function get_pathnamehash() {
835
        return $this->file_record->pathnamehash;
836
    }
837
 
838
    /**
839
     * Returns the license type of the file, it is a short name referred from license table.
840
     *
841
     * @return string
842
     */
843
    public function get_license() {
844
        return $this->file_record->license;
845
    }
846
 
847
    /**
848
     * Set license
849
     *
850
     * @param string $license license
851
     */
852
    public function set_license($license) {
853
        $filerecord = new stdClass;
854
        $filerecord->license = $license;
855
        $this->update($filerecord);
856
    }
857
 
858
    /**
859
     * Returns the author name of the file.
860
     *
861
     * @return string
862
     */
863
    public function get_author() {
864
        return $this->file_record->author;
865
    }
866
 
867
    /**
868
     * Set author
869
     *
870
     * @param string $author
871
     */
872
    public function set_author($author) {
873
        $filerecord = new stdClass;
874
        $filerecord->author = $author;
875
        $this->update($filerecord);
876
    }
877
 
878
    /**
879
     * Returns the source of the file, usually it is a url.
880
     *
881
     * @return string
882
     */
883
    public function get_source() {
884
        return $this->file_record->source;
885
    }
886
 
887
    /**
888
     * Set license
889
     *
890
     * @param string $license license
891
     */
892
    public function set_source($source) {
893
        $filerecord = new stdClass;
894
        $filerecord->source = $source;
895
        $this->update($filerecord);
896
    }
897
 
898
 
899
    /**
900
     * Returns the sort order of file
901
     *
902
     * @return int
903
     */
904
    public function get_sortorder() {
905
        return $this->file_record->sortorder;
906
    }
907
 
908
    /**
909
     * Set file sort order
910
     *
911
     * @param int $sortorder
912
     * @return int
913
     */
914
    public function set_sortorder($sortorder) {
915
        $oldorder = $this->file_record->sortorder;
916
        $filerecord = new stdClass;
917
        $filerecord->sortorder = $sortorder;
918
        $this->update($filerecord);
919
        if (!$this->is_directory()) {
920
            // Callback for file sort order change.
921
            if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
922
                foreach ($pluginsfunction as $plugintype => $plugins) {
923
                    foreach ($plugins as $pluginfunction) {
924
                        $pluginfunction($this->file_record, $oldorder, $sortorder);
925
                    }
926
                }
927
            }
928
        }
929
    }
930
 
931
    /**
932
     * Returns repository id
933
     *
934
     * @return int|null
935
     */
936
    public function get_repository_id() {
937
        if (!empty($this->repository)) {
938
            return $this->repository->id;
939
        } else {
940
            return null;
941
        }
942
    }
943
 
944
    /**
945
     * Returns repository type.
946
     *
947
     * @return mixed str|null the repository type or null if is not an external file
948
     * @since  Moodle 3.3
949
     */
950
    public function get_repository_type() {
951
 
952
        if (!empty($this->repository)) {
953
            return $this->repository->get_typename();
954
        } else {
955
            return null;
956
        }
957
    }
958
 
959
 
960
    /**
961
     * get reference file id
962
     * @return int
963
     */
964
    public function get_referencefileid() {
965
        return $this->file_record->referencefileid;
966
    }
967
 
968
    /**
969
     * Get reference last sync time
970
     * @return int
971
     */
972
    public function get_referencelastsync() {
973
        return $this->file_record->referencelastsync;
974
    }
975
 
976
    /**
977
     * Function stored_file::get_referencelifetime() is deprecated as reference
978
     * life time is no longer stored in DB or returned by repository. Each
979
     * repository should decide by itself when to synchronise the references.
980
     *
981
     * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
982
     * @see repository::sync_reference()
983
     */
984
    public function get_referencelifetime() {
985
        throw new coding_exception('Function stored_file::get_referencelifetime() can not be used any more. ' .
986
            'See repository::sync_reference().');
987
    }
988
    /**
989
     * Returns file reference
990
     *
991
     * @return string
992
     */
993
    public function get_reference() {
994
        return $this->file_record->reference;
995
    }
996
 
997
    /**
998
     * Get human readable file reference information
999
     *
1000
     * @return string
1001
     */
1002
    public function get_reference_details() {
1003
        return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
1004
    }
1005
 
1006
    /**
1007
     * Called after reference-file has been synchronized with the repository
1008
     *
1009
     * We update contenthash, filesize and status in files table if changed
1010
     * and we always update lastsync in files_reference table
1011
     *
1012
     * @param null|string $contenthash if set to null contenthash is not changed
1013
     * @param int $filesize new size of the file
1014
     * @param int $status new status of the file (0 means OK, 666 - source missing)
1015
     * @param int $timemodified last time modified of the source, if known
1016
     */
1017
    public function set_synchronized($contenthash, $filesize, $status = 0, $timemodified = null) {
1018
        if (!$this->is_external_file()) {
1019
            return;
1020
        }
1021
        $now = time();
1022
        if ($contenthash === null) {
1023
            $contenthash = $this->file_record->contenthash;
1024
        }
1025
        if ($contenthash != $this->file_record->contenthash) {
1026
            $oldcontenthash = $this->file_record->contenthash;
1027
        }
1028
        // this will update all entries in {files} that have the same filereference id
1029
        $this->fs->update_references($this->file_record->referencefileid, $now, null, $contenthash, $filesize, $status, $timemodified);
1030
        // we don't need to call update() for this object, just set the values of changed fields
1031
        $this->file_record->contenthash = $contenthash;
1032
        $this->file_record->filesize = $filesize;
1033
        $this->file_record->status = $status;
1034
        $this->file_record->referencelastsync = $now;
1035
        if ($timemodified) {
1036
            $this->file_record->timemodified = $timemodified;
1037
        }
1038
        if (isset($oldcontenthash)) {
1039
            $this->filesystem->remove_file($oldcontenthash);
1040
        }
1041
    }
1042
 
1043
    /**
1044
     * Sets the error status for a file that could not be synchronised
1045
     */
1046
    public function set_missingsource() {
1047
        $this->set_synchronized($this->file_record->contenthash, $this->file_record->filesize, 666);
1048
    }
1049
 
1050
    /**
1051
     * Send file references
1052
     *
1053
     * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
1054
     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1055
     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1056
     * @param array $options additional options affecting the file serving
1057
     */
1058
    public function send_file($lifetime, $filter, $forcedownload, $options) {
1059
        $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
1060
    }
1061
 
1062
    /**
1063
     * Imports the contents of an external file into moodle filepool.
1064
     *
1065
     * @throws moodle_exception if file could not be downloaded or is too big
1066
     * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
1067
     */
1068
    public function import_external_file_contents($maxbytes = 0) {
1069
        if ($this->repository) {
1070
            $this->repository->import_external_file_contents($this, $maxbytes);
1071
        }
1072
    }
1073
 
1074
    /**
1075
     * Gets a file relative to this file in the repository and sends it to the browser.
1076
     * Checks the function repository::supports_relative_file() to make sure it can be used.
1077
     *
1078
     * @param string $relativepath the relative path to the file we are trying to access
1079
     */
1080
    public function send_relative_file($relativepath) {
1081
        if ($this->repository && $this->repository->supports_relative_file()) {
1082
            $relativepath = clean_param($relativepath, PARAM_PATH);
1083
            $this->repository->send_relative_file($this, $relativepath);
1084
        } else {
1085
            send_file_not_found();
1086
        }
1087
    }
1088
 
1089
    /**
1090
     * Generates a thumbnail for this stored_file.
1091
     *
1092
     * If the GD library has at least version 2 and PNG support is available, the returned data
1093
     * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
1094
     * returns contents of a JPEG file with black background containing the thumbnail.
1095
     *
1096
     * @param   int $width the width of the requested thumbnail
1097
     * @param   int $height the height of the requested thumbnail
1098
     * @return  string|bool false if a problem occurs, the thumbnail image data otherwise
1099
     */
1100
    public function generate_image_thumbnail($width, $height) {
1101
        global $CFG;
1102
        require_once($CFG->libdir . '/gdlib.php');
1103
 
1104
        if (empty($width) or empty($height)) {
1105
            return false;
1106
        }
1107
 
1108
        $content = $this->get_content();
1109
 
1110
        // Fetch the image information for this image.
1111
        $imageinfo = @getimagesizefromstring($content);
1112
        if (empty($imageinfo)) {
1113
            return false;
1114
        }
1115
 
1116
        // Create a new image from the file.
1117
        $original = @imagecreatefromstring($content);
1118
 
1119
        // Generate the thumbnail.
1120
        return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
1121
    }
1122
 
1123
    /**
1124
     * Generate a resized image for this stored_file.
1125
     *
1126
     * @param int|null $width The desired width, or null to only use the height.
1127
     * @param int|null $height The desired height, or null to only use the width.
1128
     * @return string|false False when a problem occurs, else the image data.
1129
     */
1130
    public function resize_image($width, $height) {
1131
        global $CFG;
1132
        require_once($CFG->libdir . '/gdlib.php');
1133
 
1134
        $content = $this->get_content();
1135
 
1136
        // Fetch the image information for this image.
1137
        $imageinfo = @getimagesizefromstring($content);
1138
        if (empty($imageinfo)) {
1139
            return false;
1140
        }
1141
 
1142
        // Create a new image from the file.
1143
        $original = @imagecreatefromstring($content);
1144
        if (empty($original)) {
1145
            return false;
1146
        }
1147
 
1148
        // Generate the resized image.
1149
        return resize_image_from_image($original, $imageinfo, $width, $height);
1150
    }
1151
 
1152
    /**
1153
     * Check whether the supplied file is the same as this file.
1154
     *
1155
     * @param   string $path The path to the file on disk
1156
     * @return  boolean
1157
     */
1158
    public function compare_to_path($path) {
1159
        return $this->get_contenthash() === file_storage::hash_from_path($path);
1160
    }
1161
 
1162
    /**
1163
     * Check whether the supplied content is the same as this file.
1164
     *
1165
     * @param   string $content The file content
1166
     * @return  boolean
1167
     */
1168
    public function compare_to_string($content) {
1169
        return $this->get_contenthash() === file_storage::hash_from_string($content);
1170
    }
1171
 
1172
    /**
1173
     * Generate a rotated image for this stored_file based on exif information.
1174
     *
1175
     * @return array|false False when a problem occurs, else the image data and image size.
1176
     * @since Moodle 3.8
1177
     */
1178
    public function rotate_image() {
1179
        $content = $this->get_content();
1180
        $mimetype = $this->get_mimetype();
1181
 
1182
        if ($mimetype === "image/jpeg" && function_exists("exif_read_data")) {
1183
            $exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($content));
1184
            if (isset($exif['ExifImageWidth']) && isset($exif['ExifImageLength']) && isset($exif['Orientation'])) {
1185
                $rotation = [
1186
                    3 => -180,
1187
                    6 => -90,
1188
                    8 => -270,
1189
                ];
1190
                $orientation = $exif['Orientation'];
1191
                if ($orientation !== 1) {
1192
                    $source = @imagecreatefromstring($content);
1193
                    $data = @imagerotate($source, $rotation[$orientation], 0);
1194
                    if (!empty($data)) {
1195
                        if ($orientation == 1 || $orientation == 3) {
1196
                            $size = [
1197
                                'width' => $exif["ExifImageWidth"],
1198
                                'height' => $exif["ExifImageLength"],
1199
                            ];
1200
                        } else {
1201
                            $size = [
1202
                                'height' => $exif["ExifImageWidth"],
1203
                                'width' => $exif["ExifImageLength"],
1204
                            ];
1205
                        }
1206
                        imagedestroy($source);
1207
                        return [$data, $size];
1208
                    }
1209
                }
1210
            }
1211
        }
1212
        return [false, false];
1213
    }
1214
}