2 namespace TYPO3\CMS\Core\
Resource;
4 /***************************************************************
7 * (c) 2012-2013 Benjamin Mack <benni@typo3.org>
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.
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.
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.
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
31 * Representation of a specific processed version of a file. These are created by the FileProcessingService,
32 * which in turn uses helper classes for doing the actual file processing. See there for a detailed description.
34 * Objects of this class may be freshly created during runtime or being fetched from the database. The latter
35 * indicates that the file has been processed earlier and was then cached.
37 * Each processed file—besides belonging to one file—has been created for a certain task (context) and
38 * configuration. All these won't change during the lifetime of a processed file; the only thing
39 * that can change is the original file, or rather it's contents. In that case, the processed file has to
40 * be processed again. Detecting this is done via comparing the current SHA1 hash of the original file against
41 * the one it had at the time the file was processed.
42 * The configuration of a processed file indicates what should be done to the original file to create the
43 * processed version. This may include things like cropping, scaling, rotating, flipping or using some special
45 * A file may also meet the expectations set in the configuration without any processing. In that case, the
46 * ProcessedFile object still exists, but there is no physical file directly linked to it. Instead, it then
47 * redirects most method calls to the original file object. The data of these objects are also stored in the
48 * database, to indicate that no processing is required. With such files, the identifier and name fields in the
49 * database are empty to show this.
51 * @author Benjamin Mack <benni@typo3.org>
53 class ProcessedFile
extends AbstractFile
{
55 /*********************************************
56 * FILE PROCESSING CONTEXTS
57 *********************************************/
59 * Basic processing context to get a processed image with smaller
60 * width/height to render a preview
62 const CONTEXT_IMAGEPREVIEW
= 'Image.Preview';
64 * Standard processing context for the frontend, that was previously
65 * in tslib_cObj::getImgResource which only takes cropping, masking and scaling
68 const CONTEXT_IMAGECROPSCALEMASK
= 'Image.CropScaleMask';
71 * Processing context, i.e. the type of processing done
78 * @var Processing\TaskInterface
83 * @var Processing\TaskTypeRegistry
85 protected $taskTypeRegistry;
88 * Processing configuration
92 protected $processingConfiguration;
95 * Reference to the original file this processed file has been created from.
99 protected $originalFile;
102 * The SHA1 hash of the original file this processed version has been created for.
103 * Is used for detecting changes if the original file has been changed and thus
104 * we have to recreate this processed file.
108 protected $originalFileSha1;
111 * A flag that shows if this object has been updated during its lifetime, i.e. the file has been
112 * replaced with a new one.
116 protected $updated = FALSE;
119 * Constructor for a processed file object. Should normally not be used
120 * directly, use the corresponding factory methods instead.
122 * @param File $originalFile
123 * @param string $taskType
124 * @param array $processingConfiguration
125 * @param array $databaseRow
127 public function __construct(File
$originalFile, $taskType, array $processingConfiguration, array $databaseRow = NULL) {
128 $this->originalFile
= $originalFile;
129 $this->storage
= $originalFile->getStorage();
130 $this->taskType
= $taskType;
131 $this->processingConfiguration
= $processingConfiguration;
132 if (is_array($databaseRow)) {
133 $this->reconstituteFromDatabaseRecord($databaseRow);
135 $this->taskTypeRegistry
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Resource\\Processing\\TaskTypeRegistry');
139 * Creates a ProcessedFile object from a database record.
141 * @param array $databaseRow
142 * @return ProcessedFile
144 protected function reconstituteFromDatabaseRecord(array $databaseRow) {
145 $this->taskType
= empty($this->taskType
) ?
$databaseRow['task_type'] : $this->taskType
;
146 $this->processingConfiguration
= empty($this->processingConfiguration
) ?
unserialize($databaseRow['configuration']) : $this->processingConfiguration
;
148 $this->originalFileSha1
= $databaseRow['originalfilesha1'];
149 $this->identifier
= $databaseRow['identifier'];
150 $this->name
= $databaseRow['name'];
151 $this->properties
= $databaseRow;
154 /********************************
155 * VARIOUS FILE PROPERTY GETTERS
156 ********************************/
159 * Returns a unique checksum for this file's processing configuration and original file.
163 // TODO replace these usages with direct calls to the task object
164 public function calculateChecksum() {
165 return $this->getTask()->getConfigurationChecksum();
172 * Replace the current file contents with the given string
174 * @param string $contents The contents to write to the file.
175 * @return File The file object (allows chaining).
176 * @throws \BadMethodCallException
178 public function setContents($contents) {
179 throw new \
BadMethodCallException('Setting contents not possible for processed file.', 1305438528);
183 * Injects a local file, which is a processing result into the object.
185 * @param string $filePath
187 * @throws \RuntimeException
189 public function updateWithLocalFile($filePath) {
190 if ($this->identifier
=== NULL) {
191 throw new \
RuntimeException('Cannot update original file!', 1350582054);
193 // TODO this should be more generic (in fact it only works for local file paths)
194 $addedFile = $this->storage
->addFile($filePath, $this->storage
->getProcessingFolder(), $this->name
, 'replace');
196 // Update some related properties
197 $this->identifier
= $addedFile->getIdentifier();
198 $this->originalFileSha1
= $this->originalFile
->getSha1();
199 // The added file is a FileInterface object with own uid
200 // We have to unset uid otherwise the processed file couldn't be stored in database
201 // Other non-used fields were removed before database progress
202 $updateProperties = $addedFile->getProperties();
203 unset($updateProperties['uid']);
204 $this->updateProperties($updateProperties);
205 $this->deleted
= FALSE;
206 $this->updated
= TRUE;
209 /*****************************************
210 * STORAGE AND MANAGEMENT RELATED METHDOS
211 *****************************************/
213 * Returns TRUE if this file is indexed
217 public function isIndexed() {
218 // Processed files are never indexed; instead you might be looking for isPersisted()
223 * Checks whether the ProcessedFile already has an entry in sys_file_processedfile table
227 public function isPersisted() {
228 return is_array($this->properties
) && array_key_exists('uid', $this->properties
) && $this->properties
['uid'] > 0;
232 * Checks whether the ProcessedFile Object is newly created
236 public function isNew() {
237 return !$this->isPersisted();
241 * Checks whether the object since last reconstitution, and therefore
242 * needs persistence again
246 public function isUpdated() {
247 return $this->updated
;
251 * Sets a new file name
253 * @param string $name
255 public function setName($name) {
256 // Remove the existing file
257 if ($this->name
!== $name && $this->name
!== '' && $this->exists()) {
262 // TODO this is a *weird* hack that will fail if the storage is non-hierarchical!
263 $this->identifier
= $this->storage
->getProcessingFolder()->getIdentifier() . $this->name
;
265 $this->updated
= TRUE;
273 * Returns TRUE if this file is already processed.
277 public function isProcessed() {
278 return ($this->isPersisted() && !$this->needsReprocessing()) ||
$this->updated
;
282 * Getter for the Original, unprocessed File
286 public function getOriginalFile() {
287 return $this->originalFile
;
291 * Get the identifier of the file
293 * If there is no processed file in the file system (as the original file did not have to be modified e.g.
294 * when the original image is in the boundaries of the maxW/maxH stuff), then just return the identifier of
299 public function getIdentifier() {
300 return (!$this->usesOriginalFile()) ?
$this->identifier
: $this->getOriginalFile()->getIdentifier();
304 * Get the name of the file
306 * If there is no processed file in the file system (as the original file did not have to be modified e.g.
307 * when the original image is in the boundaries of the maxW/maxH stuff)
308 * then just return the name of the original file
312 public function getName() {
313 if ($this->usesOriginalFile()) {
314 return $this->originalFile
->getName();
321 * Updates properties of this object. Do not use this to reconstitute an object from the database; use
322 * reconstituteFromDatabaseRecord() instead!
324 * @param array $properties
326 public function updateProperties(array $properties) {
327 if (!is_array($this->properties
)) {
328 $this->properties
= array();
331 if (array_key_exists('uid', $properties) && \TYPO3\CMS\Core\Utility\MathUtility
::canBeInterpretedAsInteger($properties['uid'])) {
332 $this->properties
['uid'] = $properties['uid'];
335 // TODO we should have a blacklist of properties that might not be updated
336 $this->properties
= array_merge($this->properties
, $properties);
338 // TODO when should this update be done?
339 if (!$this->isUnchanged() && $this->exists()) {
340 $this->properties
= array_merge($this->properties
, $this->storage
->getFileInfo($this));
346 * Basic array function for the DB update
350 public function toArray() {
351 if ($this->usesOriginalFile()) {
352 $properties = $this->originalFile
->getProperties();
353 unset($properties['uid']);
354 unset($properties['pid']);
355 unset($properties['identifier']);
356 unset($properties['name']);
357 unset($properties['width']);
358 unset($properties['height']);
360 $properties = $this->properties
;
361 $properties['identifier'] = $this->getIdentifier();
362 $properties['name'] = $this->getName();
365 return array_merge($properties, array(
366 'storage' => $this->getStorage()->getUid(),
367 'checksum' => $this->calculateChecksum(),
368 'task_type' => $this->taskType
,
369 'configuration' => serialize($this->processingConfiguration
),
370 'original' => $this->originalFile
->getUid(),
371 'originalfilesha1' => $this->originalFileSha1
376 * Returns TRUE if this file has not been changed during processing (i.e., we just deliver the original file)
380 protected function isUnchanged() {
381 return $this->identifier
== NULL ||
$this->identifier
=== $this->originalFile
->getIdentifier();
387 public function setUsesOriginalFile() {
388 // TODO check if some of these properties can/should be set in a generic update method
389 $this->identifier
= $this->originalFile
->getIdentifier();
390 $this->updated
= TRUE;
391 $this->originalFileSha1
= $this->originalFile
->getSha1();
397 public function usesOriginalFile() {
398 return $this->isUnchanged();
402 * Returns TRUE if the original file of this file changed and the file should be processed again.
406 public function isOutdated() {
407 return $this->needsReprocessing();
413 public function delete() {
414 if ($this->isUnchanged()) {
417 return parent
::delete();
421 * Getter for file-properties
427 public function getProperty($key) {
428 // The uid always (!) has to come from this file and never the original file (see getOriginalFile() to get this)
429 if ($this->isUnchanged() && $key !== 'uid') {
430 return $this->originalFile
->getProperty($key);
432 return $this->properties
[$key];
437 * Returns the uid of this file
441 public function getUid() {
442 return $this->properties
['uid'];
447 * Checks if the ProcessedFile needs reprocessing
451 public function needsReprocessing() {
452 $fileMustBeRecreated = FALSE;
454 // processedFile does not exist
455 if (!$this->usesOriginalFile() && !$this->exists()) {
456 $fileMustBeRecreated = TRUE;
459 // hash does not match
460 if (array_key_exists('checksum', $this->properties
) && $this->calculateChecksum() !== $this->properties
['checksum']) {
461 $fileMustBeRecreated = TRUE;
464 // original file changed
465 if ($this->originalFile
->getSha1() !== $this->originalFileSha1
) {
466 $fileMustBeRecreated = TRUE;
469 if (!array_key_exists('uid', $this->properties
)) {
470 $fileMustBeRecreated = TRUE;
473 // remove outdated file
474 if ($fileMustBeRecreated && $this->exists()) {
477 return $fileMustBeRecreated;
481 * Returns the processing information
485 public function getProcessingConfiguration() {
486 return $this->processingConfiguration
;
490 * Getter for the task identifier.
494 public function getTaskIdentifier() {
495 return $this->taskType
;
499 * Returns the task object associated with this processed file.
501 * @return Processing\TaskInterface
502 * @throws \RuntimeException
504 public function getTask() {
505 if ($this->task
== NULL) {
506 $this->task
= $this->taskTypeRegistry
->getTaskForType($this->taskType
, $this, $this->processingConfiguration
);
513 * Generate the name of of the new File
517 public function generateProcessedFileNameWithoutExtension() {
518 $name = $this->originalFile
->getNameWithoutExtension();
519 $name .= '_' . $this->originalFile
->getUid();
520 $name .= '_' . $this->calculateChecksum();