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