1<?php
2namespace TYPO3\CMS\Core\Resource;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19/**
20 * File representation in the file abstraction layer.
21 */
22class File extends AbstractFile
23{
24    /**
25     * @var bool
26     */
27    protected $metaDataLoaded = false;
28
29    /**
30     * @var array
31     */
32    protected $metaDataProperties = [];
33
34    /**
35     * Set to TRUE while this file is being indexed - used to prevent some endless loops
36     *
37     * @var bool
38     */
39    protected $indexingInProgress = false;
40
41    /**
42     * Contains the names of all properties that have been update since the
43     * instantiation of this object
44     *
45     * @var array
46     */
47    protected $updatedProperties = [];
48
49    /**
50     * Constructor for a file object. Should normally not be used directly, use
51     * the corresponding factory methods instead.
52     *
53     * @param array $fileData
54     * @param ResourceStorage $storage
55     * @param array $metaData
56     */
57    public function __construct(array $fileData, ResourceStorage $storage, array $metaData = [])
58    {
59        $this->identifier = $fileData['identifier'] ?? null;
60        $this->name = $fileData['name'] ?? '';
61        $this->properties = $fileData;
62        $this->storage = $storage;
63        if (!empty($metaData)) {
64            $this->metaDataLoaded = true;
65            $this->metaDataProperties = $metaData;
66        }
67    }
68
69    /*******************************
70     * VARIOUS FILE PROPERTY GETTERS
71     *******************************/
72    /**
73     * Returns a property value
74     *
75     * @param string $key
76     * @return mixed Property value
77     */
78    public function getProperty($key)
79    {
80        if (parent::hasProperty($key)) {
81            return parent::getProperty($key);
82        }
83        $metaData = $this->_getMetaData();
84        return $metaData[$key] ?? null;
85    }
86
87    /**
88     * Checks if the file has a (metadata) property which
89     * can be retrieved by "getProperty"
90     *
91     * @param string $key
92     * @return bool
93     */
94    public function hasProperty($key)
95    {
96        if (!parent::hasProperty($key)) {
97            return array_key_exists($key, $this->_getMetaData());
98        }
99        return true;
100    }
101
102    /**
103     * Returns the properties of this object.
104     *
105     * @return array
106     */
107    public function getProperties()
108    {
109        return array_merge(parent::getProperties(), array_diff_key($this->_getMetaData(), parent::getProperties()));
110    }
111
112    /**
113     * Returns the MetaData
114     *
115     * @return array
116     * @internal
117     */
118    public function _getMetaData()
119    {
120        if (!$this->metaDataLoaded) {
121            $this->loadMetaData();
122        }
123        return $this->metaDataProperties;
124    }
125
126    /******************
127     * CONTENTS RELATED
128     ******************/
129    /**
130     * Get the contents of this file
131     *
132     * @return string File contents
133     */
134    public function getContents()
135    {
136        return $this->getStorage()->getFileContents($this);
137    }
138
139    /**
140     * Gets SHA1 hash.
141     *
142     * @return string
143     */
144    public function getSha1()
145    {
146        if (empty($this->properties['sha1'])) {
147            $this->properties['sha1'] = parent::getSha1();
148        }
149        return $this->properties['sha1'];
150    }
151
152    /**
153     * Replace the current file contents with the given string
154     *
155     * @param string $contents The contents to write to the file.
156     * @return File The file object (allows chaining).
157     */
158    public function setContents($contents)
159    {
160        $this->getStorage()->setFileContents($this, $contents);
161        return $this;
162    }
163
164    /***********************
165     * INDEX RELATED METHODS
166     ***********************/
167    /**
168     * Returns TRUE if this file is indexed
169     *
170     * @return bool|null
171     */
172    public function isIndexed()
173    {
174        return true;
175    }
176
177    /**
178     * Loads MetaData from Repository
179     */
180    protected function loadMetaData()
181    {
182        if (!$this->indexingInProgress) {
183            $this->indexingInProgress = true;
184            $this->metaDataProperties = $this->getMetaDataRepository()->findByFile($this);
185            $this->metaDataLoaded = true;
186            $this->indexingInProgress = false;
187        }
188    }
189
190    /**
191     * Updates the properties of this file, e.g. after re-indexing or moving it.
192     * By default, only properties that exist as a key in the $properties array
193     * are overwritten. If you want to explicitly unset a property, set the
194     * corresponding key to NULL in the array.
195     *
196     * NOTE: This method should not be called from outside the File Abstraction Layer (FAL)!
197     *
198     * @param array $properties
199     * @internal
200     */
201    public function updateProperties(array $properties)
202    {
203        // Setting identifier and name to update values; we have to do this
204        // here because we might need a new identifier when loading
205        // (and thus possibly indexing) a file.
206        if (isset($properties['identifier'])) {
207            $this->identifier = $properties['identifier'];
208        }
209        if (isset($properties['name'])) {
210            $this->name = $properties['name'];
211        }
212
213        if (isset($properties['uid']) && $this->properties['uid'] != 0) {
214            unset($properties['uid']);
215        }
216        foreach ($properties as $key => $value) {
217            if ($this->properties[$key] !== $value) {
218                if (!in_array($key, $this->updatedProperties)) {
219                    $this->updatedProperties[] = $key;
220                }
221                $this->properties[$key] = $value;
222            }
223        }
224        // If the mime_type property should be updated and it was changed also update the type.
225        if (array_key_exists('mime_type', $properties) && in_array('mime_type', $this->updatedProperties)) {
226            $this->updatedProperties[] = 'type';
227            unset($this->properties['type']);
228            $this->getType();
229        }
230        if (array_key_exists('storage', $properties) && in_array('storage', $this->updatedProperties)) {
231            $this->storage = ResourceFactory::getInstance()->getStorageObject($properties['storage']);
232        }
233    }
234
235    /**
236     * Updates MetaData properties
237     *
238     * @internal Do not use outside the FileAbstraction Layer classes
239     *
240     * @param array $properties
241     */
242    public function _updateMetaDataProperties(array $properties)
243    {
244        $this->metaDataProperties = array_merge($this->metaDataProperties, $properties);
245    }
246
247    /**
248     * Returns the names of all properties that have been updated in this record
249     *
250     * @return array
251     */
252    public function getUpdatedProperties()
253    {
254        return $this->updatedProperties;
255    }
256
257    /****************************************
258     * STORAGE AND MANAGEMENT RELATED METHODS
259     ****************************************/
260    /**
261     * Check if a file operation (= action) is allowed for this file
262     *
263     * @param 	string	$action, can be read, write, delete
264     * @return bool
265     */
266    public function checkActionPermission($action)
267    {
268        return $this->getStorage()->checkFileActionPermission($action, $this);
269    }
270
271    /*****************
272     * SPECIAL METHODS
273     *****************/
274    /**
275     * Creates a MD5 hash checksum based on the combined identifier of the file,
276     * the files' mimetype and the systems' encryption key.
277     * used to generate a thumbnail, and this hash is checked if valid
278     *
279     * @return string the MD5 hash
280     */
281    public function calculateChecksum()
282    {
283        return md5(
284            $this->getCombinedIdentifier() . '|' .
285            $this->getMimeType() . '|' .
286            $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
287        );
288    }
289
290    /**
291     * Returns a modified version of the file.
292     *
293     * @param string $taskType The task type of this processing
294     * @param array $configuration the processing configuration, see manual for that
295     * @return ProcessedFile The processed file
296     */
297    public function process($taskType, array $configuration)
298    {
299        return $this->getStorage()->processFile($this, $taskType, $configuration);
300    }
301
302    /**
303     * Returns an array representation of the file.
304     * (This is used by the generic listing module vidi when displaying file records.)
305     *
306     * @return array Array of main data of the file. Don't rely on all data to be present here, it's just a selection of the most relevant information.
307     */
308    public function toArray()
309    {
310        $array = [
311            'id' => $this->getCombinedIdentifier(),
312            'name' => $this->getName(),
313            'extension' => $this->getExtension(),
314            'type' => $this->getType(),
315            'mimetype' => $this->getMimeType(),
316            'size' => $this->getSize(),
317            'url' => $this->getPublicUrl(),
318            'indexed' => true,
319            'uid' => $this->getUid(),
320            'permissions' => [
321                'read' => $this->checkActionPermission('read'),
322                'write' => $this->checkActionPermission('write'),
323                'delete' => $this->checkActionPermission('delete')
324            ],
325            'checksum' => $this->calculateChecksum()
326        ];
327        foreach ($this->properties as $key => $value) {
328            $array[$key] = $value;
329        }
330        $stat = $this->getStorage()->getFileInfo($this);
331        foreach ($stat as $key => $value) {
332            $array[$key] = $value;
333        }
334        return $array;
335    }
336
337    /**
338     * @return bool
339     */
340    public function isMissing()
341    {
342        return (bool)$this->getProperty('missing');
343    }
344
345    /**
346     * @param bool $missing
347     */
348    public function setMissing($missing)
349    {
350        $this->updateProperties(['missing' => $missing ? 1 : 0]);
351    }
352
353    /**
354     * Returns a publicly accessible URL for this file
355     * When file is marked as missing or deleted no url is returned
356     *
357     * WARNING: Access to the file may be restricted by further means, e.g. some
358     * web-based authentication. You have to take care of this yourself.
359     *
360     * @param bool  $relativeToCurrentScript   Determines whether the URL returned should be relative to the current script, in case it is relative at all (only for the LocalDriver)
361     *
362     * @return string|null NULL if file is missing or deleted, the generated url otherwise
363     */
364    public function getPublicUrl($relativeToCurrentScript = false)
365    {
366        if ($this->isMissing() || $this->deleted) {
367            return null;
368        }
369        return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
370    }
371
372    /**
373     * @return Index\MetaDataRepository
374     */
375    protected function getMetaDataRepository()
376    {
377        return GeneralUtility::makeInstance(Index\MetaDataRepository::class);
378    }
379
380    /**
381     * @return Index\FileIndexRepository
382     */
383    protected function getFileIndexRepository()
384    {
385        return GeneralUtility::makeInstance(Index\FileIndexRepository::class);
386    }
387
388    /**
389     * @param bool $indexingState
390     * @internal Only for usage in Indexer
391     */
392    public function setIndexingInProgess($indexingState)
393    {
394        $this->indexingInProgress = (bool)$indexingState;
395    }
396
397    /**
398     * @param $key
399     * @internal Only for use in Repositories and indexer
400     * @return mixed
401     */
402    public function _getPropertyRaw($key)
403    {
404        return parent::getProperty($key);
405    }
406}
407