[BUGFIX] hasProperty ignores meta data properties
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / File.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011-2013 Ingo Renner <ingo@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32 /**
33 * File representation in the file abstraction layer.
34 *
35 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
36 */
37 class File extends AbstractFile {
38
39 /**
40 * File indexing status. True, if the file is indexed in the database;
41 * NULL is the default value, this means that the index status is unknown
42 *
43 * @var boolean|NULL
44 */
45 protected $indexed = NULL;
46
47 /**
48 * Tells whether to index a file or not.
49 * If yes, the file will be persisted into sys_file.
50 *
51 * @var boolean
52 */
53 protected $indexable = TRUE;
54
55 /**
56 * @var array
57 */
58 protected $metaDataProperties = array();
59
60 /**
61 * Set to TRUE while this file is being indexed - used to prevent some endless loops
62 *
63 * @var boolean
64 */
65 protected $indexingInProgress = FALSE;
66
67 /**
68 * Contains the names of all properties that have been update since the
69 * instantiation of this object
70 *
71 * @var array
72 */
73 protected $updatedProperties = array();
74
75 /**
76 * @var \TYPO3\CMS\Core\Resource\Service\IndexerService
77 */
78 protected $indexerService = NULL;
79
80 /**
81 * Constructor for a file object. Should normally not be used directly, use
82 * the corresponding factory methods instead.
83 *
84 * @param array $fileData
85 * @param ResourceStorage $storage
86 */
87 public function __construct(array $fileData, ResourceStorage $storage) {
88 $this->identifier = $fileData['identifier'];
89 $this->name = $fileData['name'];
90 $this->properties = $fileData;
91 $this->storage = $storage;
92
93 if (isset($fileData['uid']) && intval($fileData['uid']) > 0) {
94 $this->indexed = TRUE;
95 $this->loadMetaData();
96 }
97 }
98
99 /*******************************
100 * VARIOUS FILE PROPERTY GETTERS
101 *******************************/
102 /**
103 * Returns a property value
104 *
105 * @param string $key
106 * @return mixed Property value
107 */
108 public function getProperty($key) {
109 if ($this->indexed === NULL) {
110 $this->loadIndexRecord();
111 }
112 if (parent::hasProperty($key)) {
113 return parent::getProperty($key);
114 } else {
115 return array_key_exists($key, $this->metaDataProperties) ? $this->metaDataProperties[$key] : NULL;
116 }
117 }
118
119 /**
120 * Checks if the file has a (metadata) property which
121 * can be retrieved by "getProperty"
122 *
123 * @param string $key
124 * @return boolean
125 */
126 public function hasProperty($key) {
127 if (!parent::hasProperty($key)) {
128 return array_key_exists($key, $this->metaDataProperties);
129 }
130 return TRUE;
131 }
132
133
134 /**
135 * Returns the properties of this object.
136 *
137 * @return array
138 */
139 public function getProperties() {
140 if ($this->indexed === NULL) {
141 $this->loadIndexRecord();
142 }
143 return array_merge(parent::getProperties(), array_diff_key((array)$this->metaDataProperties, parent::getProperties()));
144 }
145
146 /**
147 * Returns the MetaData
148 *
149 * @return array|null
150 * @internal
151 */
152 public function _getMetaData() {
153 return $this->metaDataProperties;
154 }
155
156 /******************
157 * CONTENTS RELATED
158 ******************/
159 /**
160 * Get the contents of this file
161 *
162 * @return string File contents
163 */
164 public function getContents() {
165 return $this->getStorage()->getFileContents($this);
166 }
167
168 /**
169 * Gets SHA1 hash.
170 *
171 * @return string
172 */
173 public function getSha1() {
174 if (empty($this->properties['sha1'])) {
175 $this->properties['sha1'] = parent::getSha1();
176 }
177 return $this->properties['sha1'];
178 }
179
180 /**
181 * Replace the current file contents with the given string
182 *
183 * @param string $contents The contents to write to the file.
184 * @return File The file object (allows chaining).
185 */
186 public function setContents($contents) {
187 $this->getStorage()->setFileContents($this, $contents);
188 return $this;
189 }
190
191 /***********************
192 * INDEX RELATED METHODS
193 ***********************/
194 /**
195 * Returns TRUE if this file is indexed
196 *
197 * @return boolean|NULL
198 */
199 public function isIndexed() {
200 if ($this->indexed === NULL && !$this->indexingInProgress) {
201 $this->loadIndexRecord();
202 }
203 return $this->indexed;
204 }
205
206 /**
207 * @param bool $indexIfNotIndexed
208 *
209 * @throws \RuntimeException
210 * @return void
211 */
212 protected function loadIndexRecord($indexIfNotIndexed = TRUE) {
213 if ($this->indexed !== NULL || !$this->indexable || $this->indexingInProgress) {
214 return;
215 }
216 $this->indexingInProgress = TRUE;
217
218 $indexRecord = $this->getFileIndexRepository()->findOneByCombinedIdentifier($this->getCombinedIdentifier());
219 if ($indexRecord === FALSE && $indexIfNotIndexed) {
220 $this->getIndexerService()->updateIndexEntry($this);
221 $this->updatedProperties = array();
222 } elseif ($indexRecord !== FALSE) {
223 $this->mergeIndexRecord($indexRecord);
224 $this->indexed = TRUE;
225 $this->loadMetaData();
226 } else {
227 throw new \RuntimeException('Could not load index record for "' . $this->getIdentifier() . '"', 1321288316);
228 }
229 $this->indexingInProgress = FALSE;
230 }
231
232 /**
233 * Loads MetaData from Repository
234 */
235 protected function loadMetaData() {
236 $this->metaDataProperties = $this->getMetaDataRepository()->findByFile($this);
237 }
238
239 /**
240 * Merges the contents of this file's index record into the file properties.
241 *
242 * @param array $recordData The index record as fetched from the database
243 *
244 * @throws \InvalidArgumentException
245 * @return void
246 */
247 protected function mergeIndexRecord(array $recordData) {
248 if ($this->properties['uid'] != 0) {
249 throw new \InvalidArgumentException('uid property is already set. Cannot merge index record.', 1321023156);
250 }
251 $this->properties = array_merge($recordData, $this->properties);
252 }
253
254 /**
255 * Updates the properties of this file, e.g. after re-indexing or moving it.
256 * By default, only properties that exist as a key in the $properties array
257 * are overwritten. If you want to explicitly unset a property, set the
258 * corresponding key to NULL in the array.
259 *
260 * NOTE: This method should not be called from outside the File Abstraction Layer (FAL)!
261 *
262 * @param array $properties
263 * @return void
264 * @internal
265 */
266 public function updateProperties(array $properties) {
267 // Setting identifier and name to update values; we have to do this
268 // here because we might need a new identifier when loading
269 // (and thus possibly indexing) a file.
270 if (isset($properties['identifier'])) {
271 $this->identifier = $properties['identifier'];
272 }
273 if (isset($properties['name'])) {
274 $this->name = $properties['name'];
275 }
276 if ($this->indexed === NULL && !isset($properties['uid'])) {
277 $this->loadIndexRecord();
278 }
279 if ($this->properties['uid'] != 0 && isset($properties['uid'])) {
280 unset($properties['uid']);
281 }
282 foreach ($properties as $key => $value) {
283 if ($this->properties[$key] !== $value) {
284 if (!in_array($key, $this->updatedProperties)) {
285 $this->updatedProperties[] = $key;
286 }
287 // TODO check if we should completely remove properties that
288 // are set to NULL
289 $this->properties[$key] = $value;
290 }
291 }
292 // Updating indexing status
293 if (isset($properties['uid']) && intval($properties['uid']) > 0) {
294 $this->indexed = TRUE;
295 $this->loadMetaData();
296 }
297 if (array_key_exists('storage', $properties) && in_array('storage', $this->updatedProperties)) {
298 $this->storage = ResourceFactory::getInstance()->getStorageObject($properties['storage']);
299 }
300 }
301
302 /**
303 * Returns the names of all properties that have been updated in this record
304 *
305 * @return array
306 */
307 public function getUpdatedProperties() {
308 return $this->updatedProperties;
309 }
310
311 /****************************************
312 * STORAGE AND MANAGEMENT RELATED METHODS
313 ****************************************/
314 /**
315 * Check if a file operation (= action) is allowed for this file
316 *
317 * @param string $action, can be read, write, delete
318 * @return boolean
319 */
320 public function checkActionPermission($action) {
321 return $this->getStorage()->checkFileActionPermission($action, $this);
322 }
323
324 /*****************
325 * SPECIAL METHODS
326 *****************/
327 /**
328 * Creates a MD5 hash checksum based on the combined identifier of the file,
329 * the files' mimetype and the systems' encryption key.
330 * used to generate a thumbnail, and this hash is checked if valid
331 *
332 * @todo maybe \TYPO3\CMS\Core\Utility\GeneralUtility::hmac() could be used?
333 * @return string the MD5 hash
334 */
335 public function calculateChecksum() {
336 return md5($this->getCombinedIdentifier() . '|' . $this->getMimeType() . '|' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']);
337 }
338
339 /**
340 * Returns a modified version of the file.
341 *
342 * @param string $taskType The task type of this processing
343 * @param array $configuration the processing configuration, see manual for that
344 * @return ProcessedFile The processed file
345 */
346 public function process($taskType, array $configuration) {
347 return $this->getStorage()->processFile($this, $taskType, $configuration);
348 }
349
350 /**
351 * Returns an array representation of the file.
352 * (This is used by the generic listing module vidi when displaying file records.)
353 *
354 * @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.
355 */
356 public function toArray() {
357 $array = array(
358 'id' => $this->getCombinedIdentifier(),
359 'name' => $this->getName(),
360 'extension' => $this->getExtension(),
361 'type' => $this->getType(),
362 'mimetype' => $this->getMimeType(),
363 'size' => $this->getSize(),
364 'url' => $this->getPublicUrl(),
365 'indexed' => $this->indexed,
366 'uid' => $this->getUid(),
367 'permissions' => array(
368 'read' => $this->checkActionPermission('read'),
369 'write' => $this->checkActionPermission('write'),
370 'delete' => $this->checkActionPermission('delete')
371 ),
372 'checksum' => $this->calculateChecksum()
373 );
374 foreach ($this->properties as $key => $value) {
375 $array[$key] = $value;
376 }
377 $stat = $this->getStorage()->getFileInfo($this);
378 foreach ($stat as $key => $value) {
379 $array[$key] = $value;
380 }
381 return $array;
382 }
383
384 /**
385 * @return boolean
386 */
387 public function isIndexable() {
388 return $this->indexable;
389 }
390
391 /**
392 * @param boolean $indexable
393 */
394 public function setIndexable($indexable) {
395 $this->indexable = $indexable;
396 }
397
398 /**
399 * @return boolean
400 */
401 public function isMissing() {
402 return (bool) $this->getProperty('missing');
403 }
404
405 /**
406 * @param boolean $missing
407 */
408 public function setMissing($missing) {
409 $this->updateProperties(array('missing' => $missing ? 1 : 0));
410 }
411
412 /**
413 * Returns a publicly accessible URL for this file
414 * When file is marked as missing or deleted no url is returned
415 *
416 * WARNING: Access to the file may be restricted by further means, e.g. some
417 * web-based authentication. You have to take care of this yourself.
418 *
419 * @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)
420 *
421 * @return string
422 */
423 public function getPublicUrl($relativeToCurrentScript = FALSE) {
424 if ($this->isMissing() || $this->deleted) {
425 return FALSE;
426 } else {
427 return $this->getStorage()->getPublicUrl($this, $relativeToCurrentScript);
428 }
429 }
430
431 /**
432 * @return \TYPO3\CMS\Core\Resource\Index\MetaDataRepository
433 */
434 protected function getMetaDataRepository() {
435 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\MetaDataRepository');
436 }
437
438 /**
439 * @return \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
440 */
441 protected function getFileIndexRepository() {
442 return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository');
443 }
444
445 /**
446 * Internal function to retrieve the indexer service,
447 * if it does not exist, an instance will be created
448 *
449 * @return Index\Indexer
450 */
451 protected function getIndexerService() {
452 if ($this->indexerService === NULL) {
453 $this->indexerService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\Indexer', $this->storage);
454 }
455 return $this->indexerService;
456 }
457
458 /**
459 * @param boolean $indexingState
460 * @internal Only for usage in Indexer
461 */
462 public function setIndexingInProgess($indexingState) {
463 $this->indexingInProgress = (boolean)$indexingState;
464 }
465
466 /**
467 * @param $key
468 * @internal Only for use in Repositories and indexer
469 * @return mixed
470 */
471 public function _getPropertyRaw($key) {
472 return parent::getProperty($key);
473 }
474 }