[TASK] Refactor FAL file processing
authorAndreas Wolf <andreas.wolf@typo3.org>
Fri, 19 Oct 2012 15:20:12 +0000 (17:20 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Tue, 20 Nov 2012 22:08:56 +0000 (23:08 +0100)
File processing is a central part of TYPO3's file usage, as e.g. all
images in content elements have to be resized when they don't fit the
requirements. However, the current implementation of file processing
with FAL has several drawbacks and shortcomings, not to mention quite a
few bugs.

This patch brings a completely reworked infrastructure behind the
File Processing Service, while keeping the external API unchanged.

The processing to be done is described in tasks, which are part of a
ProcessedFile's properties. The processing itself is now moved to
processors, which could execute the tasks using different utilities,
e.g. ImageMagick or some cloud image processing service. Currently,
there is only a local image processor implementation, which relies on
ImageMagick/GraphicsMagick (i.e. uses the same configuration as the old
processing).

The processed file class now also supports safe handling of unchanged
files, i.e. files that should have been processed, but didn't need
processing.

Fixes: #43059
Fixes: #39904
Fixes: #40033
Fixes: #40669
Releases: 6.0

Change-Id: I186a46f9923dacd98be655d72be2bd89b43866c2
Reviewed-on: http://review.typo3.org/14310
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
24 files changed:
t3lib/stddb/DefaultConfiguration.php
t3lib/stddb/tables.sql
typo3/sysext/core/Classes/Resource/Driver/AbstractDriver.php
typo3/sysext/core/Classes/Resource/File.php
typo3/sysext/core/Classes/Resource/ProcessedFile.php
typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php
typo3/sysext/core/Classes/Resource/Processing/AbstractGraphicalTask.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/LocalPreviewHelper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/ProcessorInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/TaskInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/Processing/TaskTypeRegistry.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/ResourceFactory.php
typo3/sysext/core/Classes/Resource/ResourceStorage.php
typo3/sysext/core/Classes/Resource/Service/FileProcessingService.php
typo3/sysext/core/Classes/Resource/Service/ImageProcessingService.php
typo3/sysext/core/Tests/Unit/Resource/ProcessedFileRepositoryTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/tce_file.php

index 2caaf67..fead095 100644 (file)
@@ -204,6 +204,10 @@ return array(
                                        'TYPO3\\CMS\\Core\\Resource\\Filter\\FileNameFilter',
                                        'filterHiddenFilesAndFolders'
                                )
+                       ),
+                       'processingTaskTypes' => array(
+                               'Image.Preview' => 'TYPO3\\CMS\\Core\\Resource\\Processing\\ImagePreviewTask',
+                               'Image.CropScaleMask' => 'TYPO3\\CMS\\Core\\Resource\\Processing\\ImageCropScaleMaskTask'
                        )
                )
        ),
index 7027c62..3ee94b5 100644 (file)
@@ -280,6 +280,7 @@ CREATE TABLE sys_file_storage (
 CREATE TABLE sys_file (
        uid int(11) NOT NULL auto_increment,
        pid int(11) DEFAULT '0' NOT NULL,
+       # update timestamp of the database record, not the file!
        tstamp int(11) DEFAULT '0' NOT NULL,
        crdate int(11) DEFAULT '0' NOT NULL,
        cruser_id int(11) DEFAULT '0' NOT NULL,
@@ -306,6 +307,7 @@ CREATE TABLE sys_file (
        title tinytext,
        sha1 tinytext,
        size int(11) DEFAULT '0' NOT NULL,
+       # creation/modification date of the file (not the record!)
        creation_date int(11) DEFAULT '0' NOT NULL,
        modification_date int(11) DEFAULT '0' NOT NULL,
        width int(11) DEFAULT '0' NOT NULL,
@@ -325,29 +327,21 @@ CREATE TABLE sys_file (
 #
 CREATE TABLE sys_file_processedfile (
        uid int(11) NOT NULL auto_increment,
-       pid int(11) DEFAULT '0' NOT NULL,
        tstamp int(11) DEFAULT '0' NOT NULL,
        crdate int(11) DEFAULT '0' NOT NULL,
-       cruser_id int(11) DEFAULT '0' NOT NULL,
-       deleted tinyint(4) DEFAULT '0' NOT NULL,
 
        storage int(11) DEFAULT '0' NOT NULL,
        original int(11) DEFAULT '0' NOT NULL,
        identifier varchar(200) DEFAULT '' NOT NULL,
        name tinytext,
        configuration text,
-       context varchar(200) DEFAULT '' NOT NULL,
+       originalfilesha1 varchar(40) DEFAULT '' NOT NULL,
+       task_type varchar(200) DEFAULT '' NOT NULL,
        checksum varchar(255) DEFAULT '' NOT NULL,
-       is_processed varchar(200) DEFAULT '' NOT NULL,
-       extension varchar(255) DEFAULT '' NOT NULL,
-       mime_type varchar(255) DEFAULT '' NOT NULL,
-       sha1 tinytext,
-       size int(11) DEFAULT '0' NOT NULL,
-       width int(11) DEFAULT '0' NOT NULL,
-       height int(11) DEFAULT '0' NOT NULL,
+       width int(11) DEFAULT '0',
+       height int(11) DEFAULT '0',
 
-       PRIMARY KEY (uid),
-       KEY parent (pid)
+       PRIMARY KEY (uid)
 );
 
 #
index 50b1e72..77369cf 100644 (file)
@@ -443,7 +443,7 @@ abstract class AbstractDriver {
         * @param string $itemIdentifier
         * @param string $parentIdentifier
         * @param array $additionalInformation Additional information about the inspected item
-        * @return bool
+        * @return boolean
         */
        protected function applyFilterMethodsToDirectoryItem(array $filterMethods, $itemName, $itemIdentifier, $parentIdentifier, array $additionalInformation = array()) {
                foreach ($filterMethods as $filter) {
index 0797d84..9bea8ba 100644 (file)
@@ -37,14 +37,14 @@ class File extends \TYPO3\CMS\Core\Resource\AbstractFile {
         * File indexing status. True, if the file is indexed in the database;
         * NULL is the default value, this means that the index status is unknown
         *
-        * @var bool
+        * @var boolean
         */
        protected $indexed = NULL;
 
        /**
         * Set to TRUE while this file is being indexed - used to prevent some endless loops
         *
-        * @var bool
+        * @var boolean
         */
        protected $indexingInProgress = FALSE;
 
@@ -139,7 +139,7 @@ class File extends \TYPO3\CMS\Core\Resource\AbstractFile {
        /**
         * Returns TRUE if this file is indexed
         *
-        * @return bool
+        * @return boolean
         */
        public function isIndexed() {
                if ($this->indexed === NULL && !$this->indexingInProgress) {
@@ -274,12 +274,12 @@ class File extends \TYPO3\CMS\Core\Resource\AbstractFile {
        /**
         * Returns a modified version of the file.
         *
-        * @param string $context the context of the configuration (see above)
+        * @param string $taskType The task type of this processing
         * @param array $configuration the processing configuration, see manual for that
         * @return \TYPO3\CMS\Core\Resource\ProcessedFile The processed file
         */
-       public function process($context, array $configuration) {
-               return $this->getStorage()->processFile($this, $context, $configuration);
+       public function process($taskType, array $configuration) {
+               return $this->getStorage()->processFile($this, $taskType, $configuration);
        }
 
        /**
index 8809d8c..e2db5f3 100644 (file)
@@ -26,12 +26,33 @@ namespace TYPO3\CMS\Core\Resource;
  *
  * This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+
+
+
 /**
- * Representation of a specific processing of a file.
+ * Representation of a specific processed version of a file. These are created by the FileProcessingService,
+ * which in turn uses helper classes for doing the actual file processing. See there for a detailed description.
+ *
+ * Objects of this class may be freshly created during runtime or being fetched from the database. The latter
+ * indicates that the file has been processed earlier and was then cached.
+ *
+ * Each processed file—besides belonging to one file—has been created for a certain task (context) and
+ * configuration. All these won't change during the lifetime of a processed file; the only thing
+ * that can change is the original file, or rather it's contents. In that case, the processed file has to
+ * be processed again. Detecting this is done via comparing the current SHA1 hash of the original file against
+ * the one it had at the time the file was processed.
+ * The configuration of a processed file indicates what should be done to the original file to create the
+ * processed version. This may include things like cropping, scaling, rotating, flipping or using some special
+ * magic.
+ * A file may also meet the expectations set in the configuration without any processing. In that case, the
+ * ProcessedFile object still exists, but there is no physical file directly linked to it. Instead, it then
+ * redirects most method calls to the original file object. The data of these objects are also stored in the
+ * database, to indicate that no processing is required. With such files, the identifier and name fields in the
+ * database are empty to show this.
  *
  * @author Benjamin Mack <benni@typo3.org>
  */
-class ProcessedFile extends \TYPO3\CMS\Core\Resource\AbstractFile {
+class ProcessedFile extends AbstractFile {
 
        /*********************************************
         * FILE PROCESSING CONTEXTS
@@ -40,26 +61,30 @@ class ProcessedFile extends \TYPO3\CMS\Core\Resource\AbstractFile {
         * Basic processing context to get a processed image with smaller
         * width/height to render a preview
         */
-       const CONTEXT_IMAGEPREVIEW = 'image.preview';
+       const CONTEXT_IMAGEPREVIEW = 'Image.Preview';
        /**
         * Standard processing context for the frontend, that was previously
-        * in tslib_cObj::getImgResource which only takes croping, masking and scaling
+        * in tslib_cObj::getImgResource which only takes cropping, masking and scaling
         * into account
         */
-       const CONTEXT_IMAGECROPSCALEMASK = 'image.cropscalemask';
+       const CONTEXT_IMAGECROPSCALEMASK = 'Image.CropScaleMask';
+
        /**
-        * Processing context
+        * Processing context, i.e. the type of processing done
         *
         * @var string
         */
-       protected $context;
+       protected $taskType;
 
        /**
-        * check if the file is processed
-        *
-        * @var boolean
+        * @var \TYPO3\CMS\Core\Resource\Processing\TaskInterface
+        */
+       protected $task;
+
+       /**
+        * @var \TYPO3\CMS\Core\Resource\Processing\TaskTypeRegistry
         */
-       protected $processed;
+       protected $taskTypeRegistry;
 
        /**
         * Processing configuration
@@ -69,171 +94,418 @@ class ProcessedFile extends \TYPO3\CMS\Core\Resource\AbstractFile {
        protected $processingConfiguration;
 
        /**
-        * Reference to the original File object underlying this FileReference.
+        * Reference to the original file this processed file has been created from.
         *
-        * @var \TYPO3\CMS\Core\Resource\File
+        * @var File
         */
        protected $originalFile;
 
        /**
-        * Constructor for a file processing object. Should normally not be used
+        * The SHA1 hash of the original file this processed version has been created for.
+        * Is used for detecting changes if the original file has been changed and thus
+        * we have to recreate this processed file.
+        *
+        * @var string
+        */
+       protected $originalFileSha1;
+
+       /**
+        * A flag that shows if this object has been updated during its lifetime, i.e. the file has been
+        * replaced with a new one.
+        *
+        * @var boolean
+        */
+       protected $updated = FALSE;
+
+       /**
+        * Constructor for a processed file object. Should normally not be used
         * directly, use the corresponding factory methods instead.
         *
-        * @param \TYPO3\CMS\Core\Resource\File $originalFile
-        * @param string $context
+        * @param File $originalFile
+        * @param string $taskType
         * @param array $processingConfiguration
+        * @param array $databaseRow
         */
-       public function __construct(\TYPO3\CMS\Core\Resource\File $originalFile, $context, array $processingConfiguration) {
+       public function __construct(File $originalFile, $taskType, array $processingConfiguration, array $databaseRow = NULL) {
                $this->originalFile = $originalFile;
-               $this->context = $context;
+               $this->storage = $originalFile->getStorage();
+               $this->taskType = $taskType;
                $this->processingConfiguration = $processingConfiguration;
+               if (is_array($databaseRow)) {
+                       $this->reconstituteFromDatabaseRecord($databaseRow);
+               }
+               $this->taskTypeRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Processing\\TaskTypeRegistry');
        }
 
-       /*******************************
+       /**
+        * Creates a ProcessedFile object from a database record.
+        *
+        * @param array $databaseRow
+        * @return ProcessedFile
+        */
+       protected function reconstituteFromDatabaseRecord(array $databaseRow) {
+               $this->taskType = empty($this->taskType) ? $databaseRow['task_type'] : $this->taskType;
+               $this->processingConfiguration = empty($this->processingConfiguration) ? unserialize($databaseRow['configuration']) : $this->processingConfiguration;
+
+               $this->originalFileSha1 = $databaseRow['originalfilesha1'];
+               $this->identifier = $databaseRow['identifier'];
+               $this->name = $databaseRow['name'];
+               $this->properties = $databaseRow;
+       }
+
+       /********************************
         * VARIOUS FILE PROPERTY GETTERS
-        ************************/
+        ********************************/
+
        /**
-        * Returns the checksum that makes the processed configuration unique
-        * also takes the mtime and the uid into account, to find out if the
-        * process needs to be done again
+        * Returns a unique checksum for this file's processing configuration and original file.
         *
         * @return string
         */
+       // TODO replace these usages with direct calls to the task object
        public function calculateChecksum() {
-               return \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($this->originalFile->getUid() . $this->originalFile->getModificationTime() . $this->context . serialize($GLOBALS['TYPO3_CONF_VARS']['GFX']) . serialize($this->processingConfiguration));
+               return $this->getTask()->getConfigurationChecksum();
        }
 
-       /******************
+       /*******************
         * CONTENTS RELATED
-        ******************/
+        *******************/
        /**
         * Replace the current file contents with the given string
         *
         * @param string $contents The contents to write to the file.
-        * @return \TYPO3\CMS\Core\Resource\File The file object (allows chaining).
+        * @return File The file object (allows chaining).
+        * @throws \BadMethodCallException
         */
        public function setContents($contents) {
                throw new \BadMethodCallException('Setting contents not possible for processed file.', 1305438528);
        }
 
-       /****************************************
+       /**
+        * Injects a local file, which is a processing result into the object.
+        *
+        * @param string $filePath
+        * @return void
+        * @throws \RuntimeException
+        */
+       public function updateWithLocalFile($filePath) {
+               if ($this->identifier === NULL) {
+                       throw new \RuntimeException('Cannot update original file!', 1350582054);
+               }
+               // TODO this should be more generic (in fact it only works for local file paths)
+               $this->storage->addFile($filePath, $this->storage->getProcessingFolder(), $this->name, 'replace');
+               // Update some related properties
+               $this->originalFileSha1 = $this->originalFile->getSha1();
+               $this->deleted = FALSE;
+               $this->updated = TRUE;
+       }
+
+       /*****************************************
         * STORAGE AND MANAGEMENT RELATED METHDOS
-        ****************************************/
+        *****************************************/
        /**
         * Returns TRUE if this file is indexed
         *
         * @return boolean
         */
        public function isIndexed() {
+               // Processed files are never indexed; instead you might be looking for isPersisted()
                return FALSE;
        }
 
-       /*****************
-        * SPECIAL METHODS
-        *****************/
        /**
-        * Returns TRUE if this file is already processed.
+        * Checks whether the ProcessedFile already has an entry in sys_file_processedfile table
         *
         * @return boolean
         */
-       public function isProcessed() {
-               return (bool) $this->processed;
+       public function isPersisted() {
+               return is_array($this->properties) && array_key_exists('uid', $this->properties) && $this->properties['uid'] > 0;
        }
 
        /**
-        * Called when the processed file is processed
+        * Checks whether the ProcessedFile Object is newly created
         *
-        * @param boolean $isProcessed
-        * @return void
+        * @return boolean
         */
-       public function setProcessed($isProcessed) {
-               $this->processed = (bool) $isProcessed;
+       public function isNew() {
+               return !$this->isPersisted();
        }
 
        /**
-        * @return \TYPO3\CMS\Core\Resource\File
+        * Checks whether the object since last reconstitution, and therefore
+        * needs persistence again
+        *
+        * @return boolean
+        */
+       public function isUpdated() {
+               return $this->updated;
+       }
+
+       /**
+        * Sets a new file name
+        *
+        * @param string $name
+        */
+       public function setName($name) {
+               // Remove the existing file
+               if ($this->name !== $name && $this->name !== '' && $this->exists()) {
+                       $this->delete();
+               }
+
+               $this->name = $name;
+               // TODO this is a *weird* hack that will fail if the storage is non-hierarchical!
+               $this->identifier = $this->storage->getProcessingFolder()->getIdentifier() . $this->name;
+
+               $this->updated = TRUE;
+       }
+
+       /******************
+        * SPECIAL METHODS
+        ******************/
+
+       /**
+        * Returns TRUE if this file is already processed.
+        *
+        * @return boolean
+        */
+       public function isProcessed() {
+               return ($this->isPersisted() && !$this->needsReprocessing()) || $this->updated;
+       }
+
+       /**
+        * Getter for the Original, unprocessed File
+        *
+        * @return File
         */
        public function getOriginalFile() {
                return $this->originalFile;
        }
 
        /**
-        * get the identifier of the file
-        * if there is no processed file in the file system  (as the original file did not have to be modified e.g.
-        * when the original image is in the boundaries of the maxW/maxH stuff)
-        * then just return the identifier of the original file
+        * Get the identifier of the file
+        *
+        * If there is no processed file in the file system  (as the original file did not have to be modified e.g.
+        * when the original image is in the boundaries of the maxW/maxH stuff), then just return the identifier of
+        * the original file
         *
         * @return string
         */
        public function getIdentifier() {
-               if ($this->identifier) {
-                       return $this->identifier;
-               } else {
-                       return $this->originalFile->getIdentifier();
-               }
+               return (!$this->usesOriginalFile()) ? $this->identifier : $this->getOriginalFile()->getIdentifier();
        }
 
        /**
-        * get the name of the file
-        * if there is no processed file in the file system (as the original file did not have to be modified e.g.
+        * Get the name of the file
+        *
+        * If there is no processed file in the file system (as the original file did not have to be modified e.g.
         * when the original image is in the boundaries of the maxW/maxH stuff)
         * then just return the name of the original file
         *
         * @return string
         */
        public function getName() {
-               if ($this->name) {
-                       return $this->name;
-               } else {
+               if ($this->usesOriginalFile()) {
                        return $this->originalFile->getName();
+               } else {
+                       return $this->name;
                }
        }
 
        /**
-        * Updates properties of this object.
-        * This method is used to reconstitute settings from the
-        * database into this object after being intantiated.
+        * Updates properties of this object. Do not use this to reconstitute an object from the database; use
+        * reconstituteFromDatabaseRecord() instead!
         *
         * @param array $properties
         */
        public function updateProperties(array $properties) {
-               if ($properties['name']) {
-                       $this->name = $properties['name'];
-               }
-               if ($properties['identifier']) {
-                       $this->identifier = $properties['identifier'];
+               if (!is_array($this->properties)) {
+                       $this->properties = array();
                }
-               if (isset($properties['is_processed']) && $properties['is_processed'] > 0) {
-                       $this->processed = TRUE;
-               }
-               if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($properties['storage'])) {
-                       $this->setStorage($properties['storage']);
+
+               if (array_key_exists('uid', $properties) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($properties['uid'])) {
+                       $this->properties['uid'] = $properties['uid'];
                }
+
+               // TODO we should have a blacklist of properties that might not be updated
                $this->properties = array_merge($this->properties, $properties);
+
+               // TODO when should this update be done?
+               if (!$this->isUnchanged() && $this->exists()) {
+                       $this->properties = array_merge($this->properties, $this->storage->getFileInfo($this));
+               }
+
        }
 
        /**
-        * basic array function for the DB update
+        * Basic array function for the DB update
         *
         * @return array
         */
        public function toArray() {
-               // @todo: define what we need here
-               return array(
+               if ($this->usesOriginalFile()) {
+                       $properties = $this->originalFile->getProperties();
+                       unset($properties['uid']);
+                       unset($properties['pid']);
+                       unset($properties['identifier']);
+                       unset($properties['name']);
+                       unset($properties['width']);
+                       unset($properties['height']);
+               } else {
+                       $properties = $this->properties;
+                       $properties['identifier'] = $this->getIdentifier();
+                       $properties['name'] = $this->getName();
+               }
+
+               return array_merge($properties, array(
                        'storage' => $this->getStorage()->getUid(),
-                       'identifier' => $this->getIdentifier(),
-                       'name' => $this->getName(),
-                       'is_processed' => intval($this->processed),
                        'checksum' => $this->calculateChecksum(),
-                       'context' => $this->context,
+                       'task_type' => $this->taskType,
                        'configuration' => serialize($this->processingConfiguration),
                        'original' => $this->originalFile->getUid(),
-                       'width' => $this->getProperty('width'),
-                       'height' => $this->getProperty('height')
-               );
+                       'originalfilesha1' => $this->originalFileSha1
+               ));
        }
 
-}
+       /**
+        * Returns TRUE if this file has not been changed during processing (i.e., we just deliver the original file)
+        *
+        * @return boolean
+        */
+       protected function isUnchanged() {
+               return $this->identifier == NULL || $this->identifier === $this->originalFile->getIdentifier();
+       }
+
+       /**
+        * @return void
+        */
+       public function setUsesOriginalFile() {
+               // TODO check if some of these properties can/should be set in a generic update method
+               $this->identifier = $this->originalFile->getIdentifier();
+               $this->updated = TRUE;
+               $this->originalFileSha1 = $this->originalFile->getSha1();
+       }
+
+       /**
+        * @return boolean
+        */
+       public function usesOriginalFile() {
+               return $this->isUnchanged();
+       }
+
+       /**
+        * Returns TRUE if the original file of this file changed and the file should be processed again.
+        *
+        * @return boolean
+        */
+       public function isOutdated() {
+               return $this->needsReprocessing();
+       }
+
+       /**
+        * @return boolean
+        */
+       public function delete() {
+               if ($this->isUnchanged()) {
+                       return FALSE;
+               }
+               return parent::delete();
+       }
+
+       /**
+        * Getter for file-properties
+        *
+        * @param string $key
+        *
+        * @return mixed
+        */
+       public function getProperty($key) {
+               if ($this->isUnchanged()) {
+                       return $this->originalFile->getProperty($key);
+               } else {
+                       return $this->properties[$key];
+               }
+       }
+
+
+       /**
+        * Checks if the ProcessedFile needs reprocessing
+        *
+        * @return boolean
+        */
+       public function needsReprocessing() {
+               $fileMustBeRecreated = FALSE;
+
+               // processedFile does not exist
+               if (!$this->usesOriginalFile() && !$this->exists()) {
+                       $fileMustBeRecreated = TRUE;
+               }
+
+               // hash does not match
+               if (array_key_exists('checksum', $this->properties) && $this->calculateChecksum() !== $this->properties['checksum'])  {
+                       $fileMustBeRecreated = TRUE;
+               }
+
+               // original file changed
+               if ($this->originalFile->getSha1() !== $this->originalFileSha1) {
+                       $fileMustBeRecreated = TRUE;
+               }
+
+               if (!array_key_exists('uid', $this->properties)) {
+                       $fileMustBeRecreated = TRUE;
+               }
+
+               // remove outdated file
+               if ($fileMustBeRecreated && $this->exists()) {
+                       $this->delete();
+               }
+               return $fileMustBeRecreated;
+       }
+
+       /**
+        * Returns the processing information
+        *
+        * @return array
+        */
+       public function getProcessingConfiguration() {
+               return $this->processingConfiguration;
+       }
 
+       /**
+        * Getter for the task identifier.
+        *
+        * @return string
+        */
+       public function getTaskIdentifier() {
+               return $this->taskType;
+       }
+
+       /**
+        * Returns the task object associated with this processed file.
+        *
+        * @return Processing\TaskInterface
+        * @throws \RuntimeException
+        */
+       public function getTask() {
+               if ($this->task == NULL) {
+                       $this->task = $this->taskTypeRegistry->getTaskForType($this->taskType, $this, $this->processingConfiguration);
+               }
+
+               return $this->task;
+       }
+
+       /**
+        * Generate the name of of the new File
+        *
+        * @return string
+        */
+       public function generateProcessedFileNameWithoutExtension() {
+               $name = $this->originalFile->getNameWithoutExtension();
+               $name .= '_' . $this->originalFile->getUid();
+               $name .= '_' . $this->calculateChecksum();
+
+               return $name;
+       }
+
+}
 
 ?>
\ No newline at end of file
index a134bed..2f2eb74 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace TYPO3\CMS\Core\Resource;
+use \TYPO3\CMS\Core\Utility;
 
 /***************************************************************
  *  Copyright notice
@@ -33,7 +34,7 @@ namespace TYPO3\CMS\Core\Resource;
  * @author Benjamin Mack <benni@typo3.org>
  * @author Ingmar Schlecht <ingmar@typo3.org>
  */
-class ProcessedFileRepository extends \TYPO3\CMS\Core\Resource\AbstractRepository {
+class ProcessedFileRepository extends AbstractRepository {
 
        /**
         * The main object type of this class. In some cases (fileReference) this
@@ -53,64 +54,127 @@ class ProcessedFileRepository extends \TYPO3\CMS\Core\Resource\AbstractRepositor
        protected $table = 'sys_file_processedfile';
 
        /**
-        * Creates an object managed by this repository.
-        *
-        * @param array $databaseRow
-        * @return \TYPO3\CMS\Core\Resource\File
+        * @var ResourceFactory
         */
-       protected function createDomainObject(array $databaseRow) {
-               return $this->factory->getFileObject($databaseRow['uid'], $databaseRow);
+       protected $resourceFactory;
+
+       /**
+        * @var \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected $databaseConnection;
+
+       /**
+        * Creates this object.
+        */
+       public function __construct() {
+               $this->resourceFactory = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
+               $this->databaseConnection = $GLOBALS['TYPO3_DB'];
        }
 
        /**
-        * Loads index-data into processedFileObject
+        * Creates a ProcessedFile object from a file object and a processing configuration
         *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFileObject
-        * @return boolean
+        * @param FileInterface $originalFile
+        * @param string $taskType
+        * @param array $configuration
+        * @return ProcessedFile
         */
-       public function populateDataOfProcessedFileObject(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFileObject) {
-               /** @var $GLOBALS['TYPO3_DB'] \TYPO3\CMS\Core\Database\DatabaseConnection */
-               $recordData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', $this->table, 'original=' . intval($processedFileObject->getOriginalFile()->getUid()) . ' AND checksum=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($processedFileObject->calculateChecksum(), $this->table) . ' AND deleted=0');
-               // Update the properties if the data was found
-               if (is_array($recordData)) {
-                       $processedFileObject->updateProperties($recordData);
-                       return TRUE;
-               } else {
-                       return FALSE;
-               }
+       public function createNewProcessedFileObject(FileInterface $originalFile, $taskType, array $configuration) {
+               return Utility\GeneralUtility::makeInstance(
+                       $this->objectType,
+                       $originalFile,
+                       $taskType,
+                       $configuration
+               );
+       }
+
+       /**
+        * @param array $databaseRow
+        * @return ProcessedFile
+        */
+       protected function createDomainObject(array $databaseRow) {
+               $originalFile = $this->resourceFactory->getFileObject(intval($databaseRow['original']));
+               $originalFile->setStorage($this->resourceFactory->getStorageObject($originalFile->getProperty('storage')));
+               $taskType = $databaseRow['task_type'];
+               $configuration = unserialize($databaseRow['configuration']);
+
+               return Utility\GeneralUtility::makeInstance(
+                       $this->objectType,
+                       $originalFile,
+                       $taskType,
+                       $configuration,
+                       $databaseRow
+               );
        }
 
        /**
         * Adds a processedfile object in the database
         *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
+        * @param ProcessedFile $processedFile
         * @return void
         */
        public function add($processedFile) {
-               $insertFields = $processedFile->toArray();
-               $insertFields['crdate'] = ($insertFields['tstamp'] = time());
-               // @todo: make sure that the toArray method only
-               // contains fields that actually *exist* in the table
-               $GLOBALS['TYPO3_DB']->exec_INSERTquery($this->table, $insertFields);
+               if ($processedFile->isPersisted()) {
+                       $this->update($processedFile);
+               } else {
+                       $insertFields = $processedFile->toArray();
+                       $insertFields['crdate'] = $insertFields['tstamp'] = time();
+                       $insertFields = $this->cleanUnavailableColumns($insertFields);
+                       $this->databaseConnection->exec_INSERTquery($this->table, $insertFields);
+                       $uid = $this->databaseConnection->sql_insert_id();
+                       $processedFile->updateProperties(array('uid' => $uid));
+               }
        }
 
        /**
         * Updates an existing file object in the database
         *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
+        * @param ProcessedFile $processedFile
         * @return void
         */
        public function update($processedFile) {
-               $uid = intval($processedFile->getProperty('uid'));
-               if ($uid > 0) {
-                       // @todo: make sure that the toArray method only
-                       // contains fields that actually *exist* in the table
-                       $updateFields = $processedFile->toArray();
+               if ($processedFile->isPersisted()) {
+                       $uid = intval($processedFile->getProperty('uid'));
+                       $updateFields = $this->cleanUnavailableColumns($processedFile->toArray());
                        $updateFields['tstamp'] = time();
-                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($this->table, 'uid=' . $uid, $updateFields);
+                       $this->databaseConnection->exec_UPDATEquery($this->table, 'uid=' . intval($uid), $updateFields);
+               }
+       }
+
+       /**
+        * @param File $file
+        * @param string $taskType The task that should be executed on the file
+        * @param array $configuration
+        *
+        * @return ProcessedFile
+        */
+       public function findOneByOriginalFileAndTaskTypeAndConfiguration(FileInterface $file, $taskType, array $configuration) {
+               $databaseRow = $this->databaseConnection->exec_SELECTgetSingleRow(
+                       '*',
+                       $this->table,
+                       'original=' . intval($file->getUid()) .
+                               ' AND task_type=' . $this->databaseConnection->fullQuoteStr($taskType, $this->table) .
+                               ' AND configuration=' . $this->databaseConnection->fullQuoteStr(serialize($configuration), $this->table)
+               );
+
+               if (is_array($databaseRow)) {
+                       $processedFile = $this->createDomainObject($databaseRow);
+               } else {
+                       $processedFile = $this->createNewProcessedFileObject($file, $taskType, $configuration);
                }
+               return $processedFile;
        }
 
+       /**
+        * Removes all array keys which cannot be persisted
+        *
+        * @param array $data
+        *
+        * @return array
+        */
+       protected function cleanUnavailableColumns(array $data) {
+               return array_intersect_key($data, $this->databaseConnection->admin_get_fields($this->table));
+       }
 }
 
 
diff --git a/typo3/sysext/core/Classes/Resource/Processing/AbstractGraphicalTask.php b/typo3/sysext/core/Classes/Resource/Processing/AbstractGraphicalTask.php
new file mode 100644 (file)
index 0000000..2b8fb0b
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource, \TYPO3\CMS\Core\Utility;
+
+/**
+ * Abstract base implementation of a task.
+ *
+ * If you extend this class, make sure that you redefine the member variables $type and $name
+ * or set them in the constructor. Otherwise your task won't be recognized by the system and several
+ * things will fail.
+ *
+ */
+abstract class AbstractGraphicalTask extends AbstractTask {
+
+       /**
+        * Sets parameters needed in the checksum. Can be overridden to add additional parameters to the checksum.
+        * This should include all parameters that could possibly vary between different task instances, e.g. the
+        * TYPO3 image configuration in TYPO3_CONF_VARS[GFX] for graphic processing tasks.
+        *
+        * @return array
+        */
+       protected function getChecksumData() {
+               return array_merge(
+                       parent::getChecksumData(),
+                       array(serialize($GLOBALS['TYPO3_CONF_VARS']['GFX']))
+               );
+       }
+
+       /**
+        * Returns the filename
+        *
+        * @return string
+        */
+       public function getTargetFilename() {
+               if ($this->targetFile->getOriginalFile()->getExtension() === 'jpg') {
+                       $targetFileExtension = 'jpg';
+               } else {
+                       $targetFileExtension = 'png';
+               }
+
+               return $this->targetFile->getOriginalFile()->getNameWithoutExtension()
+                       . '_' . $this->getConfigurationChecksum()
+                       . '.' . $targetFileExtension;
+       }
+
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php b/typo3/sysext/core/Classes/Resource/Processing/AbstractTask.php
new file mode 100644 (file)
index 0000000..9eb11e2
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource, \TYPO3\CMS\Core\Utility;
+
+/**
+ * Abstract base implementation of a task.
+ *
+ * If you extend this class, make sure that you redefine the member variables $type and $name
+ * or set them in the constructor. Otherwise your task won't be recognized by the system and several
+ * things will fail.
+ *
+ */
+abstract class AbstractTask implements TaskInterface {
+
+       /**
+        * @var array
+        */
+       protected $checksumData = array();
+
+       /**
+        * @var Resource\ProcessedFile
+        */
+       protected $targetFile;
+
+       /**
+        * @var array
+        */
+       protected $configuration;
+
+       /**
+        * @var string
+        */
+       protected $type;
+
+       /**
+        * @var string
+        */
+       protected $name;
+
+       /**
+        * @var boolean
+        */
+       protected $executed = FALSE;
+
+       /**
+        * @var boolean
+        */
+       protected $successful;
+
+       /**
+        * @param Resource\ProcessedFile $targetFile
+        * @param array $configuration
+        */
+       public function __construct(Resource\ProcessedFile $targetFile, array $configuration) {
+               $this->targetFile = $targetFile;
+               $this->configuration = $configuration;
+       }
+
+       /**
+        * Sets parameters needed in the checksum. Can be overridden to add additional parameters to the checksum.
+        * This should include all parameters that could possibly vary between different task instances, e.g. the
+        * TYPO3 image configuration in TYPO3_CONF_VARS[GFX] for graphic processing tasks.
+        *
+        * @return array
+        */
+       protected function getChecksumData() {
+               return array(
+                       $this->targetFile->getOriginalFile()->getUid(),
+                       $this->getType() . '.' . $this->getName(),
+                       serialize($this->configuration)
+               );
+       }
+
+       /**
+        * Returns the checksum for this task's configuration, also taking the file and task type into account.
+        *
+        * @return string
+        */
+       public function getConfigurationChecksum() {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5(implode('|', $this->getChecksumData()));
+       }
+
+       /**
+        * Returns the filename
+        *
+        * @return string
+        */
+       public function getTargetFilename() {
+               return $this->targetFile->getNameWithoutExtension()
+                       . '_' . $this->getConfigurationChecksum()
+                       . '.' . $this->targetFile->getExtension();
+       }
+
+       /**
+        * Returns the name of this task
+        *
+        * @return string
+        */
+       public function getName() {
+               return $this->name;
+       }
+
+       /**
+        * Returns the type of this task
+        *
+        * @return string
+        */
+       public function getType() {
+               return $this->type;
+       }
+
+       /**
+        * @return Resource\ProcessedFile
+        */
+       public function getTargetFile() {
+               return $this->targetFile;
+       }
+
+       /**
+        * @return array
+        */
+       public function getConfiguration() {
+               return $this->configuration;
+       }
+
+       /**
+        * Checks if the given configuration is sensible for this task, i.e. if all required parameters
+        * are given, within the boundaries and don't conflict with each other.
+        *
+        * @param array $configuration
+        * @return boolean
+        */
+       abstract protected function isValidConfiguration(array $configuration);
+
+       /**
+        * Returns TRUE if this task has been executed, no matter if the execution was successful.
+        *
+        * @return boolean
+        */
+       public function isExecuted() {
+               return $this->executed;
+       }
+
+       /**
+        * Set this task executed. This is used by the Processors in order to transfer the state of this task to
+        * the file processing service.
+        *
+        * @param boolean $successful Set this to FALSE if executing the task failed
+        * @return void
+        */
+       public function setExecuted($successful) {
+               $this->executed = TRUE;
+               $this->successful = $successful;
+       }
+
+       /**
+        * Returns TRUE if this task has been successfully executed. Only call this method if the task has been processed
+        * at all.
+        * @return boolean
+        * @throws \LogicException If the task has not been executed already
+        */
+       public function isSuccessful() {
+               if (!$this->executed) {
+                       throw new \LogicException('Task has not been executed; cannot determine success.', 1352549235);
+               }
+
+               return $this->successful;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php b/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php
new file mode 100644 (file)
index 0000000..87e5ad3
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource;
+
+/**
+ * A task that takes care of cropping, scaling and/or masking an image.
+ */
+class ImageCropScaleMaskTask extends AbstractGraphicalTask {
+
+       /**
+        * @var string
+        */
+       protected $type = 'Image';
+
+       /**
+        * @var string
+        */
+       protected $name = 'CropScaleMask';
+
+       /**
+        * @return string
+        */
+       public function getTargetFileName() {
+               return 'csm_' . parent::getTargetFilename();
+       }
+
+       /**
+        * Checks if the given configuration is sensible for this task, i.e. if all required parameters
+        * are given, within the boundaries and don't conflict with each other.
+        *
+        * @param array $configuration
+        * @return boolean
+        */
+       protected function isValidConfiguration(array $configuration) {
+               // TODO: Implement isValidConfiguration() method.
+       }
+
+       public function fileNeedsProcessing() {
+               // TODO: Implement fileNeedsProcessing() method.
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php b/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php
new file mode 100644 (file)
index 0000000..69d65f9
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource;
+
+/**
+ * A task for generating an image preview.
+ */
+class ImagePreviewTask extends AbstractGraphicalTask {
+
+       /**
+        * @var string
+        */
+       protected $type = 'Image';
+
+       /**
+        * @var string
+        */
+       protected $name = 'Preview';
+
+       /**
+        * Returns the target filename for this task.
+        *
+        * @return string
+        */
+       public function getTargetFileName() {
+               return 'preview_' . parent::getTargetFilename();
+       }
+
+       /**
+        * Checks if the given configuration is sensible for this task, i.e. if all required parameters
+        * are given, within the boundaries and don't conflict with each other.
+        *
+        * @param array $configuration
+        * @return boolean
+        */
+       protected function isValidConfiguration(array $configuration) {
+               /**
+                * Checks to perform:
+                * - width and/or height given, integer values?
+                */
+       }
+
+       /**
+        * Returns TRUE if the file has to be processed at all, such as e.g. the original file does.
+        *
+        * Note: This does not indicate if the concrete ProcessedFile attached to this task has to be (re)processed.
+        * This check is done in ProcessedFile::isOutdated(). TODO isOutdated()/needsReprocessing()?
+        *
+        * @return boolean
+        */
+       public function fileNeedsProcessing() {
+               // TODO: Implement fileNeedsProcessing() method.
+
+               /**
+                * Checks to perform:
+                * - width/height smaller than image, keeping aspect ratio?
+                */
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php b/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
new file mode 100644 (file)
index 0000000..6e541f8
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource, \TYPO3\CMS\Core\Utility;
+
+/**
+ * Helper class to locally perform a crop/scale/mask task with the TYPO3 image processing classes.
+ */
+class LocalCropScaleMaskHelper {
+       /**
+        * @var LocalImageProcessor
+        */
+       protected $processor;
+
+
+       /**
+        * @param LocalImageProcessor $processor
+        */
+       public function __construct(LocalImageProcessor $processor) {
+               $this->processor = $processor;
+       }
+
+       /**
+        * This method actually does the processing of files locally
+        *
+        * Takes the original file (for remote storages this will be fetched from the remote server),
+        * does the IM magic on the local server by creating a temporary typo3temp/ file,
+        * copies the typo3temp/ file to the processing folder of the target storage and
+        * removes the typo3temp/ file.
+        *
+        * @param TaskInterface $task
+        * @return array
+        */
+       public function process(TaskInterface $task) {
+               $result = NULL;
+               $targetFile = $task->getTargetFile();
+
+               $originalFileName = $targetFile->getOriginalFile()->getForLocalProcessing(FALSE);
+               /** @var $gifBuilder \TYPO3\CMS\Frontend\Imaging\GifBuilder */
+               $gifBuilder = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Imaging\\GifBuilder');
+               $gifBuilder->init();
+
+               $configuration = $targetFile->getProcessingConfiguration();
+               $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration);
+
+               $options = $this->getConfigurationForImageCropScaleMask($targetFile, $gifBuilder);
+
+               // Normal situation (no masking)
+               if (!(is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im'])) {
+                               // the result info is an array with 0=width,1=height,2=extension,3=filename
+                       $result = $gifBuilder->imageMagickConvert(
+                               $originalFileName,
+                               $configuration['fileExtension'],
+                               $configuration['width'],
+                               $configuration['height'],
+                               $configuration['additionalParameters'],
+                               $configuration['frame'],
+                               $options
+                       );
+               } else {
+                       $targetFileName = $this->getFilenameForImageCropScaleMask($targetFile);
+                       $temporaryFileName = $gifBuilder->tempPath . $targetFileName;
+                       $maskImage = $configuration['maskImages']['maskImage'];
+                       $maskBackgroundImage = $configuration['maskImages']['backgroundImage'];
+                       if ($maskImage instanceof Resource\FileInterface && $maskBackgroundImage instanceof Resource\FileInterface) {
+                               $negate = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_negate_mask'] ? ' -negate' : '';
+                               $temporaryExtension = 'png';
+                               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_gif']) {
+                                       // If ImageMagick version 5+
+                                       $temporaryExtension = $gifBuilder->gifExtension;
+                               }
+                               $tempFileInfo = $gifBuilder->imageMagickConvert(
+                                       $originalFileName,
+                                       $temporaryExtension,
+                                       $configuration['width'],
+                                       $configuration['height'],
+                                       $configuration['additionalParameters'],
+                                       $configuration['frame'],
+                                       $options
+                               );
+                               if (is_array($tempFileInfo)) {
+                                       $maskBottomImage = $configuration['maskImages']['maskBottomImage'];
+                                       if ($maskBottomImage instanceof $maskBottomImage) {
+                                               $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask'];
+                                       } else {
+                                               $maskBottomImageMask = NULL;
+                                       }
+
+                                       //      Scaling:        ****
+                                       $tempScale = array();
+                                       $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
+                                       $command = $this->modifyImageMagickStripProfileParameters($command, $configuration);
+                                       $tmpStr = $gifBuilder->randomName();
+                                       //      m_mask
+                                       $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension;
+                                       $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(TRUE), $tempScale['m_mask'], $command . $negate);
+                                       //      m_bgImg
+                                       $tempScale['m_bgImg'] = $tmpStr . '_bgImg.' . trim($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_noloss']);
+                                       $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command);
+                                       //      m_bottomImg / m_bottomImg_mask
+                                       if ($maskBottomImage instanceof Resource\FileInterface && $maskBottomImageMask instanceof Resource\FileInterface) {
+                                               $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension;
+                                               $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command);
+                                               $tempScale['m_bottomImg_mask'] = ($tmpStr . '_bottomImg_mask.') . $temporaryExtension;
+                                               $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command . $negate);
+                                               // BEGIN combining:
+                                               // The image onto the background
+                                               $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
+                                       }
+                                       // The image onto the background
+                                       $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName);
+                                       // Unlink the temp-images...
+                                       foreach ($tempScale as $tempFile) {
+                                               if (@is_file($tempFile)) {
+                                                       unlink($tempFile);
+                                               }
+                                       }
+                               }
+                               $result = $tempFileInfo;
+                       }
+               }
+               // check if the processing really generated a new file
+               if ($result !== NULL) {
+                       if ($result[3] !== $originalFileName) {
+                               $result = array(
+                                       'width' => $result[0],
+                                       'height' => $result[1],
+                                       'filePath' => $result[3],
+                               );
+                       } else {
+                               // No file was generated
+                               $result = NULL;
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * @param Resource\ProcessedFile $processedFile
+        * @param \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder
+        *
+        * @return array
+        */
+       protected function getConfigurationForImageCropScaleMask(Resource\ProcessedFile $processedFile, \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder) {
+               $configuration = $processedFile->getProcessingConfiguration();
+
+               if ($configuration['useSample']) {
+                       $gifBuilder->scalecmd = '-sample';
+               }
+               $options = array();
+               if ($configuration['maxWidth']) {
+                       $options['maxW'] = $configuration['maxWidth'];
+               }
+               if ($configuration['maxHeight']) {
+                       $options['maxH'] = $configuration['maxHeight'];
+               }
+               if ($configuration['minWidth']) {
+                       $options['minW'] = $configuration['minWidth'];
+               }
+               if ($configuration['minHeight']) {
+                       $options['minH'] = $configuration['minHeight'];
+               }
+
+               $options['noScale'] = $configuration['noScale'];
+
+               return $options;
+       }
+
+       /**
+        * Returns the filename for a cropped/scaled/masked file.
+        *
+        * @param Resource\ProcessedFile $processedFile
+        * @return string
+        */
+       protected function getFilenameForImageCropScaleMask(Resource\ProcessedFile $processedFile) {
+
+               $configuration = $processedFile->getProcessingConfiguration();
+               $targetFileExtension = $processedFile->getOriginalFile()->getExtension();
+               $processedFileExtension = $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'] ? 'png' : 'gif';
+               if (is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im'] && $processedFile->getOriginalFile()->getExtension() != $processedFileExtension) {
+                       $targetFileExtension = 'jpg';
+               } elseif ($configuration['fileExtension']) {
+                       $targetFileExtension = $configuration['fileExtension'];
+               }
+
+               return $processedFile->generateProcessedFileNameWithoutExtension() . '.' . ltrim(trim($targetFileExtension), '.');
+       }
+
+       /**
+        * Modifies the parameters for ImageMagick for stripping of profile information.
+        *
+        * @param string $parameters The parameters to be modified (if required)
+        * @param array $configuration The TypoScript configuration of [IMAGE].file
+        * @return string
+        */
+       protected function modifyImageMagickStripProfileParameters($parameters, array $configuration) {
+                       // Strips profile information of image to save some space:
+               if (isset($configuration['stripProfile'])) {
+                       if ($configuration['stripProfile']) {
+                               $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_stripProfileCommand'] . $parameters;
+                       } else {
+                               $parameters .= '###SkipStripProfile###';
+                       }
+               }
+               return $parameters;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php b/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php
new file mode 100644 (file)
index 0000000..025ead4
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource, \TYPO3\CMS\Core\Utility;
+
+/**
+ * Processes Local Images files
+ */
+class LocalImageProcessor implements ProcessorInterface {
+
+       /**
+        * @var \TYPO3\CMS\Core\Log\Logger
+        */
+       protected $logger;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
+               $logManager = Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager');
+               $this->logger = $logManager->getLogger(__CLASS__);
+       }
+
+       /**
+        * Returns TRUE if this processor can process the given task.
+        *
+        * @param TaskInterface $task
+        * @return boolean
+        */
+       public function canProcessTask(TaskInterface $task) {
+               $canProcessTask = $task->getType() === 'Image';
+               $canProcessTask = $canProcessTask & in_array($task->getName(), array('Preview', 'CropScaleMask'));
+               return $canProcessTask;
+       }
+
+       /**
+        * Processes the given task.
+        *
+        * @param TaskInterface $task
+        * @throws \InvalidArgumentException
+        */
+       public function processTask(TaskInterface $task) {
+               if (!$this->canProcessTask($task)) {
+                       throw new \InvalidArgumentException('Cannot process task of type "' . $task->getType() . '.' . $task->getName() . '"', 1350570621);
+               }
+               $helper = $this->getHelperByTaskName($task->getName());
+               try {
+                       $result = $helper->process($task);
+                       if ($result === NULL) {
+                               $task->setExecuted(TRUE);
+                               $task->getTargetFile()->setUsesOriginalFile();
+                       } elseif (file_exists($result['filePath'])) {
+                               $task->setExecuted(TRUE);
+                               $graphicalFunctions = $this->getGraphicalFunctionsObject();
+                               $imageDimensions = $graphicalFunctions->getImageDimensions($result['filePath']);
+
+                               $task->getTargetFile()->setName($task->getTargetFileName());
+                               $task->getTargetFile()->updateProperties(
+                                       array('width' => $imageDimensions[0], 'height' => $imageDimensions[1], 'size' => filesize($result['filePath']))
+                               );
+                               $task->getTargetFile()->updateWithLocalFile($result['filePath']);
+                       } else {
+                               // Seems we have no valid processing result
+                               $task->setExecuted(FALSE);
+                       }
+               } catch (\Exception $e) {
+                       $task->setExecuted(FALSE);
+               }
+       }
+
+       /**
+        * @param string $taskName
+        * @return LocalCropScaleMaskHelper|LocalPreviewHelper
+        * @throws \InvalidArgumentException
+        */
+       protected function getHelperByTaskName($taskName) {
+               switch ($taskName) {
+                       case 'Preview':
+                               $helper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Processing\LocalPreviewHelper', $this);
+                       break;
+                       case 'CropScaleMask':
+                               $helper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Processing\LocalCropScaleMaskHelper', $this);
+                       break;
+                       default:
+                               throw new \InvalidArgumentException('Cannot find helper for task name: "' . $taskName . '"', 1353401352);
+               }
+
+               return $helper;
+       }
+
+       /**
+        * Escapes a file name so it can safely be used on the command line.
+        *
+        * @param string $inputName filename to safeguard, must not be empty
+        * @return string $inputName escaped as needed
+        *
+        * @internal Don't use this method from outside the LocalImageProcessor!
+        */
+       public function wrapFileName($inputName) {
+               if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
+                       $currentLocale = setlocale(LC_CTYPE, 0);
+                       setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
+                       $escapedInputName = escapeshellarg($inputName);
+                       setlocale(LC_CTYPE, $currentLocale);
+               } else {
+                       $escapedInputName = escapeshellarg($inputName);
+               }
+               return $escapedInputName;
+       }
+
+       /**
+        * Creates error image based on gfx/notfound_thumb.png
+        * Requires GD lib enabled, otherwise it will exit with the three
+        * textstrings outputted as text. Outputs the image stream to browser and exits!
+        *
+        * @param string $filename Name of the file
+        * @param string $textline1 Text line 1
+        * @param string $textline2 Text line 2
+        * @param string $textline3 Text line 3
+        * @return void
+        * @throws \RuntimeException
+        *
+        * @internal Don't use this method from outside the LocalImageProcessor!
+        */
+       public function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3) {
+               if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
+                       throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
+               }
+               // Creates the basis for the error image
+               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
+                       $im = imagecreatefrompng(PATH_typo3 . 'gfx/notfound_thumb.png');
+               } else {
+                       $im = imagecreatefromgif(PATH_typo3 . 'gfx/notfound_thumb.gif');
+               }
+               // Sets background color and print color.
+               $white = imageColorAllocate($im, 255, 255, 255);
+               $black = imageColorAllocate($im, 0, 0, 0);
+               // Prints the text strings with the build-in font functions of GD
+               $x = 0;
+               $font = 0;
+               if ($textline1) {
+                       imagefilledrectangle($im, $x, 9, 56, 16, $white);
+                       imageString($im, $font, $x, 9, $textline1, $black);
+               }
+               if ($textline2) {
+                       imagefilledrectangle($im, $x, 19, 56, 26, $white);
+                       imageString($im, $font, $x, 19, $textline2, $black);
+               }
+               if ($textline3) {
+                       imagefilledrectangle($im, $x, 29, 56, 36, $white);
+                       imageString($im, $font, $x, 29, substr($textline3, -14), $black);
+               }
+               // Outputting the image stream and exit
+               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
+                       imagePng($im, $filename);
+               } else {
+                       imageGif($im, $filename);
+               }
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Imaging\GraphicalFunctions
+        */
+       protected function getGraphicalFunctionsObject() {
+               static $graphicalFunctionsObject = NULL;
+
+               if ($graphicalFunctionsObject === NULL) {
+                       $graphicalFunctionsObject = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Imaging\\GraphicalFunctions');
+               }
+
+               return $graphicalFunctionsObject;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalPreviewHelper.php b/typo3/sysext/core/Classes/Resource/Processing/LocalPreviewHelper.php
new file mode 100644 (file)
index 0000000..03ff874
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource, \TYPO3\CMS\Core\Utility;
+
+/**
+ * Helper for creating local image previews using TYPO3s image processing classes.
+ */
+class LocalPreviewHelper {
+       /**
+        * @var LocalImageProcessor
+        */
+       protected $processor;
+
+       /**
+        * @param LocalImageProcessor $processor
+        */
+       public function __construct(LocalImageProcessor $processor) {
+               $this->processor = $processor;
+       }
+
+       /**
+        * This method actually does the processing of files locally
+        *
+        * takes the original file (on remote storages this will be fetched from the remote server)
+        * does the IM magic on the local server by creating a temporary typo3temp/ file
+        * copies the typo3temp/ file to the processing folder of the target storage
+        * removes the typo3temp/ file
+        *
+        * @param TaskInterface $task
+        * @return array
+        */
+       public function process(TaskInterface $task) {
+               $targetFile = $task->getTargetFile();
+
+                       // Merge custom configuration with default configuration
+               $configuration = array_merge(array('width' => 64, 'height' => 64), $task->getConfiguration());
+               $configuration['width'] = Utility\MathUtility::forceIntegerInRange($configuration['width'], 1, 1000);
+               $configuration['height'] = Utility\MathUtility::forceIntegerInRange($configuration['height'], 1, 1000);
+
+               $originalFileName = $targetFile->getOriginalFile()->getForLocalProcessing(FALSE);
+
+                       // Create a temporary file in typo3temp/
+               if ($targetFile->getOriginalFile()->getExtension() === 'jpg') {
+                       $targetFileExtension = '.jpg';
+               } else {
+                       $targetFileExtension = '.png';
+               }
+
+                       // Create the thumb filename in typo3temp/preview_....jpg
+               $temporaryFileName = Utility\GeneralUtility::tempnam('preview_') . $targetFileExtension;
+                       // Check file extension
+               if ($targetFile->getOriginalFile()->getType() != Resource\File::FILETYPE_IMAGE &&
+                       !Utility\GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $targetFile->getOriginalFile()->getExtension())) {
+                               // Create a default image
+                       $this->processor->getTemporaryImageWithText($temporaryFileName, 'Not imagefile!', 'No ext!', $targetFile->getOriginalFile()->getName());
+               } else {
+                               // Create the temporary file
+                       if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im']) {
+                               $parameters = '-sample ' . $configuration['width'] . 'x' . $configuration['height'] . ' '
+                                       . $this->processor->wrapFileName($originalFileName) . '[0] ' . $this->processor->wrapFileName($temporaryFileName);
+
+                               $cmd = Utility\GeneralUtility::imageMagickCommand('convert', $parameters) . ' 2>&1';
+                               Utility\CommandUtility::exec($cmd);
+
+                               if (!file_exists($temporaryFileName)) {
+                                               // Create a error gif
+                                       $this->processor->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', $targetFile->getOriginalFile()->getName());
+                               }
+                       }
+               }
+
+               return array(
+                       'filePath' => $temporaryFileName,
+               );
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/ProcessorInterface.php b/typo3/sysext/core/Classes/Resource/Processing/ProcessorInterface.php
new file mode 100644 (file)
index 0000000..24f9156
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Interface for file processors. All classes capable of processing a file have to implement this interface.
+ */
+interface ProcessorInterface {
+
+       /**
+        * Returns TRUE if this processor can process the given task.
+        *
+        * @param TaskInterface $task
+        * @return boolean
+        */
+       public function canProcessTask(TaskInterface $task);
+
+       /**
+        * Processes the given task and sets the processing result in the task object.
+        *
+        * @param TaskInterface $task
+        * @return void
+        */
+       public function processTask(TaskInterface $task);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/TaskInterface.php b/typo3/sysext/core/Classes/Resource/Processing/TaskInterface.php
new file mode 100644 (file)
index 0000000..bdbab42
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use \TYPO3\CMS\Core\Resource;
+
+/**
+ * A task is a unit of work that can be performed by a file processor. This may include multiple steps in any order,
+ * details depend on the configuration of the task and the tools the processor uses.
+ *
+ * Each task has a type and a name. The type describes the category of the task, like "image" and "video". If your task
+ * is generic or applies to multiple types of files, use "general".
+ *
+ * A task also already has to know the target file it should be executed on, so there is no "abstract" task that just
+ * specifies the steps to be executed without a concrete file. However, new tasks can easily be created from an
+ * existing task object.
+ */
+interface TaskInterface {
+
+       /**
+        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $targetFile
+        * @param array $configuration
+        */
+       public function __construct(Resource\ProcessedFile $targetFile, array $configuration);
+
+       /**
+        * Returns the name of this task.
+        *
+        * @return string
+        */
+       public function getName();
+
+       /**
+        * Returns the type of this task.
+        *
+        * @return string
+        */
+       public function getType();
+
+       /**
+        * Returns the processed file this task is executed on.
+        *
+        * @return Resource\ProcessedFile
+        */
+       public function getTargetFile();
+
+       /**
+        * Returns the configuration for this task.
+        *
+        * @return array
+        */
+       public function getConfiguration();
+
+       /**
+        * Returns the configuration checksum of this task.
+        *
+        * @return string
+        */
+       public function getConfigurationChecksum();
+
+       /**
+        * Returns the name the processed file should have in the filesystem.
+        *
+        * @return string
+        */
+       public function getTargetFileName();
+
+       /**
+        * Returns TRUE if the file has to be processed at all, such as e.g. the original file does.
+        *
+        * Note: This does not indicate if the concrete ProcessedFile attached to this task has to be (re)processed.
+        * This check is done in ProcessedFile::isOutdated(). TODO isOutdated()/needsReprocessing()?
+        *
+        * @return boolean
+        */
+       public function fileNeedsProcessing();
+
+       /**
+        * Returns TRUE if this task has been executed, no matter if the execution was successful.
+        *
+        * @return boolean
+        */
+       public function isExecuted();
+
+       /**
+        * Mark this task as executed. This is used by the Processors in order to transfer the state of this task to
+        * the file processing service.
+        *
+        * @param boolean $successful Set this to FALSE if executing the task failed
+        * @return void
+        */
+       public function setExecuted($successful);
+
+       /**
+        * Returns TRUE if this task has been successfully executed. Only call this method if the task has been processed
+        * at all.
+        *
+        * @return boolean
+        * @throws \LogicException If the task has not been executed already
+        */
+       public function isSuccessful();
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Resource/Processing/TaskTypeRegistry.php b/typo3/sysext/core/Classes/Resource/Processing/TaskTypeRegistry.php
new file mode 100644 (file)
index 0000000..d88c070
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Processing;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Andreas Wolf <andreas.wolf@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * The registry for task types.
+ */
+class TaskTypeRegistry implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * @var array
+        */
+       protected $registeredTaskTypes = array();
+
+       /**
+        * Register task types from configuration
+        */
+       public function __construct() {
+               $this->registeredTaskTypes = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['processingTaskTypes'];
+       }
+
+       /**
+        * Returns the class that implements the given task type.
+        *
+        * @param string $taskType
+        * @return string
+        */
+       protected function getClassForTaskType($taskType) {
+               return isset($this->registeredTaskTypes[$taskType]) ? $this->registeredTaskTypes[$taskType] : NULL;
+       }
+
+       /**
+        * @param string $taskType
+        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
+        * @param array $processingConfiguration
+        * @return TaskInterface
+        * @throws \RuntimeException
+        */
+       public function getTaskForType($taskType, \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, array $processingConfiguration) {
+               $taskClass = $this->getClassForTaskType($taskType);
+               if ($taskClass === NULL) {
+                       throw new \RuntimeException('Unknown processing task "' . $taskType . '"');
+               }
+
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($taskClass,
+                       $processedFile, $processingConfiguration
+               );
+       }
+}
+
+?>
\ No newline at end of file
index fca328e..07b5541 100644 (file)
@@ -4,7 +4,7 @@ namespace TYPO3\CMS\Core\Resource;
 /***************************************************************
  *  Copyright notice
  *
- *  (c) 2011 Andreas Wolf <andreas.wolf@ikt-werk.de>
+ *  (c) 2011 Andreas Wolf <andreas.wolf@typo3.org>
  *  All rights reserved
  *
  *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -32,7 +32,7 @@ namespace TYPO3\CMS\Core\Resource;
  *
  * NOTE: This class is part of the lowlevel FAL API and should not be used from outside the FAL package.
  *
- * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
+ * @author Andreas Wolf <andreas.wolf@typo3.org>
  */
 class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
 
@@ -442,26 +442,6 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                $fileReferenceObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\FileReference', $fileReferenceData);
                return $fileReferenceObject;
        }
-
-       /**
-        * Generates a new object of the type \TYPO3\CMS\Core\Resource\ProcessedFile
-        * additionally checks if this processed file already exists in the DB
-        *
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $originalFileObject
-        * @param string $context The context the file is processed in
-        * @param array $configuration The processing configuration
-        * @return \TYPO3\CMS\Core\Resource\ProcessedFile
-        */
-       public function getProcessedFileObject(\TYPO3\CMS\Core\Resource\FileInterface $originalFileObject, $context, array $configuration) {
-               /** @var \TYPO3\CMS\Core\Resource\ProcessedFile $processedFileObject */
-               $processedFileObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ProcessedFile', $originalFileObject, $context, $configuration);
-               /* @var \TYPO3\CMS\Core\Resource\ProcessedFileRepository $repository */
-               $repository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ProcessedFileRepository');
-               // Check if this file already exists in the DB
-               $repository->populateDataOfProcessedFileObject($processedFileObject);
-               return $processedFileObject;
-       }
-
 }
 
 
index a74f1b1..33f4ad1 100644 (file)
@@ -4,7 +4,7 @@ namespace TYPO3\CMS\Core\Resource;
 /***************************************************************
  *  Copyright notice
  *
- *  (c) 2011 Andreas Wolf <andreas.wolf@ikt-werk.de>
+ *  (c) 2011 Andreas Wolf <andreas.wolf@typo3.org>
  *  All rights reserved
  *
  *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -61,7 +61,7 @@ namespace TYPO3\CMS\Core\Resource;
 /**
  * File storage
  *
- * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
+ * @author Andreas Wolf <andreas.wolf@typo3.org>
  * @author Ingmar Schlecht <ingmar@typo3.org>
  */
 class ResourceStorage {
@@ -86,8 +86,6 @@ class ResourceStorage {
        const SIGNAL_PostFolderDelete = 'postFolderDelete';
        const SIGNAL_PreFolderRename = 'preFolderRename';
        const SIGNAL_PostFolderRename = 'postFolderRename';
-       const SIGNAL_PreFileProcess = 'preFileProcess';
-       const SIGNAL_PostFileProcess = 'postFileProcess';
        const SIGNAL_PreGeneratePublicUrl = 'preGeneratePublicUrl';
        /**
         * The storage driver instance belonging to this storage.
@@ -175,7 +173,7 @@ class ResourceStorage {
        /**
         * whether this storage is online or offline in this request
         *
-        * @var bool
+        * @var boolean
         */
        protected $isOnline = NULL;
 
@@ -744,27 +742,21 @@ class ResourceStorage {
        }
 
        /**
-        * Returns a publicly accessible URL for a file.
+        * Passes a file to the File Processing Services and returns the resulting ProcessedFile object.
         *
         * @param \TYPO3\CMS\Core\Resource\FileInterface $fileObject The file object
         * @param string $context
         * @param array $configuration
+        *
         * @return \TYPO3\CMS\Core\Resource\ProcessedFile
+        * @throws \InvalidArgumentException
         */
        public function processFile(\TYPO3\CMS\Core\Resource\FileInterface $fileObject, $context, array $configuration) {
-               $processedFile = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getProcessedFileObject($fileObject, $context, $configuration);
-               // set the storage of the processed file
-               $processedFile->setStorage($this);
-               // Pre-process the file by an accordant slot
-               $this->emitPreFileProcess($processedFile, $fileObject, $context, $configuration);
-               // Only handle the file is not processed yet
-               // (maybe modified or already processed by a signal)
-               // or (in case of preview images) already in the DB/in the processing folder
-               if (!$processedFile->isProcessed()) {
-                       $processedFile = $this->getFileProcessingService()->process($processedFile, $fileObject, $context, $configuration);
-               }
-               // Post-process (enrich) the file by an accordant slot
-               $this->emitPostFileProcess($processedFile, $fileObject, $context, $configuration);
+               if ($fileObject->getStorage() !== $this) {
+                       throw new \InvalidArgumentException('Cannot process files of foreign storage', 1353401835);
+               }
+               $processedFile = $this->getFileProcessingService()->processFile($fileObject, $this, $context, $configuration);
+
                return $processedFile;
        }
 
@@ -1445,7 +1437,7 @@ class ResourceStorage {
         * @param bool $deleteRecursively
         * @throws \RuntimeException
         * @throws \TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException
-        * @return bool
+        * @return boolean
         */
        public function deleteFolder($folderObject, $deleteRecursively = FALSE) {
                if (!$this->checkFolderActionPermission('remove', $folderObject)) {
@@ -1499,8 +1491,8 @@ class ResourceStorage {
        /**
         * Returns TRUE if the specified folder exists.
         *
-        * @param $identifier
-        * @return bool
+        * @param string $identifier
+        * @return boolean
         */
        public function hasFolder($identifier) {
                return $this->driver->folderExists($identifier);
@@ -1805,30 +1797,6 @@ class ResourceStorage {
        }
 
        /**
-        * Emits file pre-processing signal.
-        *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param string $context
-        * @param array $configuration
-        */
-       protected function emitPreFileProcess(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, $context, array $configuration = array()) {
-               $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileProcess, array($this, $this->driver, $processedFile, $file, $context, $configuration));
-       }
-
-       /**
-        * Emits file post-processing signal.
-        *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param $context
-        * @param array $configuration
-        */
-       protected function emitPostFileProcess(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, $context, array $configuration = array()) {
-               $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileProcess, array($this, $this->driver, $processedFile, $file, $context, $configuration));
-       }
-
-       /**
         * Emits file pre-processing signal when generating a public url for a file or folder.
         *
         * @param \TYPO3\CMS\Core\Resource\ResourceInterface $resourceObject
index 242b347..042d023 100644 (file)
@@ -26,6 +26,10 @@ namespace TYPO3\CMS\Core\Resource\Service;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+
+use TYPO3\CMS\Core\Resource;
+use TYPO3\CMS\Core\Utility;
+
 /**
  * File processing service
  *
@@ -34,347 +38,146 @@ namespace TYPO3\CMS\Core\Resource\Service;
 class FileProcessingService {
 
        /**
-        * @var \TYPO3\CMS\Core\Resource\ResourceStorage
+        * @var Resource\ResourceStorage
         */
        protected $storage;
 
        /**
-        * @var \TYPO3\CMS\Core\Resource\Driver\AbstractDriver
+        * @var Resource\Driver\AbstractDriver
         */
        protected $driver;
 
        /**
+        * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
+        */
+       protected $signalSlotDispatcher;
+
+       /**
+        * @var \TYPO3\CMS\Core\Log\Logger
+        */
+       protected $logger;
+
+       const SIGNAL_PreFileProcess = 'preFileProcess';
+       const SIGNAL_PostFileProcess = 'postFileProcess';
+
+       /**
         * Creates this object.
         *
-        * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
-        * @param \TYPO3\CMS\Core\Resource\Driver\AbstractDriver $driver
+        * @param Resource\ResourceStorage $storage
+        * @param Resource\Driver\AbstractDriver $driver
         */
-       public function __construct(\TYPO3\CMS\Core\Resource\ResourceStorage $storage, \TYPO3\CMS\Core\Resource\Driver\AbstractDriver $driver) {
+       public function __construct(Resource\ResourceStorage $storage, Resource\Driver\AbstractDriver $driver) {
                $this->storage = $storage;
                $this->driver = $driver;
+
+               /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
+               $logManager = Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager');
+               $this->logger = $logManager->getLogger(__CLASS__);
        }
 
        /**
-        * Processes the file.
+        * Processes a file
         *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param string $context
+        * @param Resource\FileInterface $fileObject The file object
+        * @param Resource\ResourceStorage $targetStorage The storage to store the processed file in
+        * @param string $taskType
         * @param array $configuration
-        * @return \TYPO3\CMS\Core\Resource\ProcessedFile
+        *
+        * @return Resource\ProcessedFile
+        * @throws \InvalidArgumentException
         */
-       public function process(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, $context, array $configuration = array()) {
-               switch ($context) {
-               case $processedFile::CONTEXT_IMAGEPREVIEW:
-                       $this->processImagePreview($processedFile, $file, $configuration);
-                       break;
-               case $processedFile::CONTEXT_IMAGECROPSCALEMASK:
-                       $this->processImageCropResizeMask($processedFile, $file, $configuration);
-                       break;
-               default:
-                       throw new \RuntimeException('Unknown processing context "' . $context . '"');
-               }
-               if ($processedFile->isProcessed()) {
-                       // DB-query to update all info
-                       /** @var $processedFileRepository \TYPO3\CMS\Core\Resource\ProcessedFileRepository */
-                       $processedFileRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ProcessedFileRepository');
-                       if ($processedFile->hasProperty('uid') && $processedFile->getProperty('uid') > 0) {
-                               $processedFileRepository->update($processedFile);
-                       } else {
-                               $processedFileRepository->add($processedFile);
-                       }
+       public function processFile(Resource\FileInterface $fileObject, Resource\ResourceStorage $targetStorage, $taskType, $configuration) {
+               /** @var $processedFileRepository Resource\ProcessedFileRepository */
+               $processedFileRepository = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ProcessedFileRepository');
+
+               $processedFile = $processedFileRepository->findOneByOriginalFileAndTaskTypeAndConfiguration($fileObject, $taskType, $configuration);
+
+               // set the storage of the processed file
+               // Pre-process the file
+               $this->emitPreFileProcess($processedFile, $fileObject, $taskType, $configuration);
+
+               // Only handle the file if it is not processed yet
+               // (maybe modified or already processed by a signal)
+               // or (in case of preview images) already in the DB/in the processing folder
+               if (!$processedFile->isProcessed()) {
+                       $this->process($processedFile, $targetStorage);
                }
+
+               // Post-process (enrich) the file
+               $this->emitPostFileProcess($processedFile, $fileObject, $taskType, $configuration);
+
                return $processedFile;
        }
 
        /**
-        * This method actually does the processing of files locally
+        * Processes the file
         *
-        * takes the original file (on remote storages this will be fetched from the remote server)
-        * does the IM magic on the local server by creating a temporary typo3temp/ file
-        * copies the typo3temp/ file to the processingfolder of the target storage
-        * removes the typo3temp/ file
+        * @param Resource\ProcessedFile $processedFile
+        * @param Resource\ResourceStorage $targetStorage The storage to put the processed file into
         *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
-        * @param array $configuration
-        * @return void
+        * @throws \RuntimeException
         */
-       protected function processImagePreview(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, array $configuration) {
-               // Merge custom configuration with default configuration
-               $configuration = array_merge(array('width' => 64, 'height' => 64), $configuration);
-               $configuration['width'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($configuration['width'], 1, 1000);
-               $configuration['height'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($configuration['height'], 1, 1000);
-               $originalFileName = $file->getForLocalProcessing(FALSE);
-               // Create a temporary file in typo3temp/
-               if ($file->getExtension() === 'jpg') {
-                       $targetFileExtension = '.jpg';
-               } else {
-                       $targetFileExtension = '.png';
+       protected function process(Resource\ProcessedFile $processedFile, Resource\ResourceStorage $targetStorage) {
+               $targetFolder = $targetStorage->getProcessingFolder();
+               if (!is_object($targetFolder)) {
+                       throw new \RuntimeException('Could not get processing folder for storage ' . $this->storage->getName(), 1350514301);
                }
-               $targetFolder = $this->storage->getProcessingFolder();
-               $targetFileName = 'preview_' . $processedFile->calculateChecksum() . $targetFileExtension;
-               // Do the actual processing
-               if (!$targetFolder->hasFile($targetFileName)) {
-                       // Create the thumb filename in typo3temp/preview_....jpg
-                       $temporaryFileName = \TYPO3\CMS\Core\Utility\GeneralUtility::tempnam('preview_') . $targetFileExtension;
-                       // Check file extension
-                       if ($file->getType() != $file::FILETYPE_IMAGE && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $file->getExtension())) {
-                               // Create a default image
-                               $this->getTemporaryImageWithText($temporaryFileName, 'Not imagefile!', 'No ext!', $file->getName());
-                       } else {
-                               // Create the temporary file
-                               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im']) {
-                                       $parameters = '-sample ' . $configuration['width'] . 'x' . $configuration['height'] . ' ' . $this->wrapFileName($originalFileName) . '[0] ' . $this->wrapFileName($temporaryFileName);
-                                       $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('convert', $parameters);
-                                       \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
-                                       if (!file_exists($temporaryFileName)) {
-                                               // Create a error gif
-                                               $this->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', $file->getName());
-                                       }
-                               }
-                       }
-                       // Temporary image could have been created
-                       if (file_exists($temporaryFileName)) {
-                               \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryFileName);
-                               // Copy the temporary file to the processedFolder
-                               // this is done here, as the driver can do this without worrying
-                               // about existing ProcessedFile objects
-                               // or permissions in the storage
-                               // for "remote" storages this means "uploading" the file to the storage again
-                               // for the virtual storage, it is merely a thing of "copying a file from typo3temp/ to typo3temp/_processed_"
-                               $this->driver->addFile($temporaryFileName, $targetFolder, $targetFileName, $processedFile);
-                               // Remove the temporary file as it's not needed anymore
-                               \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile($temporaryFileName);
-                               $processedFile->setProcessed(TRUE);
-                       }
-               } else {
-                       // the file already exists, nothing to do locally, but still mark the file as processed and save the data
-                       // and update the fields, as they might have not been set
-                       if ($processedFile->getProperty('identifier') == '') {
-                               $identifier = $targetFolder->getIdentifier() . $targetFileName;
-                               $processedFile->updateProperties(array('name' => $targetFileName, 'identifier' => $identifier));
-                       }
-                       $processedFile->setProcessed(TRUE);
-               }
-       }
 
-       /**
-        * Escapes a file name so it can safely be used on the command line.
-        *
-        * @param string $inputName filename to safeguard, must not be empty
-        * @return string $inputName escaped as needed
-        */
-       protected function wrapFileName($inputName) {
-               if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
-                       $currentLocale = setlocale(LC_CTYPE, 0);
-                       setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
-                       $escapedInputName = escapeshellarg($inputName);
-                       setlocale(LC_CTYPE, $currentLocale);
-               } else {
-                       $escapedInputName = escapeshellarg($inputName);
+               // We only have to trigger the file processing if the file either is new, does not exist or the
+               // original file has changed since the last processing run (the last case has to trigger a reprocessing
+               // even if the original file was used until now)
+               if ($processedFile->isNew() || (!$processedFile->usesOriginalFile() && !$processedFile->exists()) ||
+                       $processedFile->isOutdated()) {
+
+                       $task = $processedFile->getTask();
+                       /** @var $processor Resource\Processing\LocalImageProcessor */
+                       $processor = Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Processing\LocalImageProcessor');
+                       $processor->processTask($task);
+
+                       if ($processedFile->isProcessed()) {
+                               /** @var $processedFileRepository Resource\ProcessedFileRepository */
+                               $processedFileRepository = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ProcessedFileRepository');
+                               $processedFileRepository->add($processedFile);
+                       }
                }
-               return $escapedInputName;
        }
 
        /**
-        * Creates error image based on gfx/notfound_thumb.png
-        * Requires GD lib enabled, otherwise it will exit with the three
-        * textstrings outputted as text. Outputs the image stream to browser and exits!
+        * Get the SignalSlot dispatcher
         *
-        * @param string $filename Name of the file
-        * @param string $textline1 Text line 1
-        * @param string $textline2 Text line 2
-        * @param string $textline3 Text line 3
-        * @return void
+        * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
         */
-       protected function getTemporaryImageWithText($filename, $textline1, $textline2, $textline3) {
-               if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
-                       throw new \RuntimeException('TYPO3 Fatal Error: No gdlib. ' . $textline1 . ' ' . $textline2 . ' ' . $textline3, 1270853952);
-               }
-               // Creates the basis for the error image
-               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
-                       $im = imagecreatefrompng(PATH_typo3 . 'gfx/notfound_thumb.png');
-               } else {
-                       $im = imagecreatefromgif(PATH_typo3 . 'gfx/notfound_thumb.gif');
-               }
-               // Sets background color and print color.
-               $white = imageColorAllocate($im, 255, 255, 255);
-               $black = imageColorAllocate($im, 0, 0, 0);
-               // Prints the text strings with the build-in font functions of GD
-               $x = 0;
-               $font = 0;
-               if ($textline1) {
-                       imagefilledrectangle($im, $x, 9, 56, 16, $white);
-                       imageString($im, $font, $x, 9, $textline1, $black);
-               }
-               if ($textline2) {
-                       imagefilledrectangle($im, $x, 19, 56, 26, $white);
-                       imageString($im, $font, $x, 19, $textline2, $black);
-               }
-               if ($textline3) {
-                       imagefilledrectangle($im, $x, 29, 56, 36, $white);
-                       imageString($im, $font, $x, 29, substr($textline3, -14), $black);
-               }
-               // Outputting the image stream and exit
-               if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png']) {
-                       imagePng($im, $filename);
-               } else {
-                       imageGif($im, $filename);
+       protected function getSignalSlotDispatcher() {
+               if (!isset($this->signalSlotDispatcher)) {
+                       $this->signalSlotDispatcher = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager')
+                               ->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
                }
+               return $this->signalSlotDispatcher;
        }
 
        /**
-        * This method actually does the processing of files locally
+        * Emits file pre-processing signal.
         *
-        * takes the original file (on remote storages this will be fetched from the remote server)
-        * does the IM magic on the local server by creating a temporary typo3temp/ file
-        * copies the typo3temp/ file to the processingfolder of the target storage
-        * removes the typo3temp/ file
-        *
-        * @param \TYPO3\CMS\Core\Resource\ProcessedFile $processedFile
-        * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+        * @param Resource\ProcessedFile $processedFile
+        * @param Resource\FileInterface $file
+        * @param string $context
         * @param array $configuration
-        * @return void
         */
-       protected function processImageCropResizeMask(\TYPO3\CMS\Core\Resource\ProcessedFile $processedFile, \TYPO3\CMS\Core\Resource\FileInterface $file, array $configuration) {
-               // checks to see if m (the mask array) is defined
-               $doMasking = is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['im'];
-               // @todo: is it ok that we use tslib (=FE) here?
-               /** @var $gifBuilder \TYPO3\CMS\Frontend\Imaging\GifBuilder */
-               $gifBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Imaging\\GifBuilder');
-               $gifBuilder->init();
-               // @todo: this is not clean yet
-               if (!trim($configuration['fileExtension'])) {
-                       $configuration['fileExtension'] = 'web';
-                       $targetFileExtension = $file->getExtension();
-               } elseif ($doMasking) {
-                       $targetFileExtension = $file->getExtension() == $gifBuilder->gifExtension ? $gifBuilder->gifExtension : 'jpg';
-               } else {
-                       $targetFileExtension = $configuration['fileExtension'];
-               }
-               $originalFileName = $file->getForLocalProcessing(FALSE);
-               $targetFolder = $this->storage->getProcessingFolder();
-               $targetFileName = 'previewcrm_' . $processedFile->calculateChecksum() . '.' . $targetFileExtension;
-               // @todo: implement meaningful TempFileIndex
-               if ($configuration['useSample']) {
-                       $gifBuilder->scalecmd = '-sample';
-               }
-               $options = array();
-               if ($configuration['maxWidth']) {
-                       $options['maxW'] = $configuration['maxWidth'];
-               }
-               if ($configuration['maxHeight']) {
-                       $options['maxH'] = $configuration['maxHeight'];
-               }
-               if ($configuration['minWidth']) {
-                       $options['minW'] = $configuration['minWidth'];
-               }
-               if ($configuration['minHeight']) {
-                       $options['minH'] = $configuration['minHeight'];
-               }
-               $options['noScale'] = $configuration['noScale'];
-               $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration);
-               // Do the actual processing
-               if (!$targetFolder->hasFile($targetFileName)) {
-                       if (!$doMasking) {
-                               // Normal situation (no masking)
-                               // the result info is an array with 0=width,1=height,2=extension,3=filename
-                               list($targetWidth, $targetHeight, $targetExtension, $temporaryFileName) = $gifBuilder->imageMagickConvert($originalFileName, $configuration['fileExtension'], $configuration['width'], $configuration['height'], $configuration['additionalParameters'], $configuration['frame'], $options);
-                       } else {
-                               $temporaryFileName = $gifBuilder->tempPath . $targetFileName;
-                               $maskImage = $configuration['maskImages']['maskImage'];
-                               $maskBackgroundImage = $configuration['maskImages']['backgroundImage'];
-                               if ($maskImage instanceof \TYPO3\CMS\Core\Resource\FileInterface && $maskBackgroundImage instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
-                                       $negate = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_negate_mask'] ? ' -negate' : '';
-                                       $temporaryExtension = 'png';
-                                       if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_gif']) {
-                                               // If ImageMagick version 5+
-                                               $temporaryExtension = $gifBuilder->gifExtension;
-                                       }
-                                       $tempFileInfo = $gifBuilder->imageMagickConvert($originalFileName, $temporaryExtension, $configuration['width'], $configuration['height'], $configuration['additionalParameters'], $configuration['frame'], $options);
-                                       if (is_array($tempFileInfo)) {
-                                               $maskBottomImage = $configuration['maskImages']['maskBottomImage'];
-                                               if ($maskBottomImage instanceof $maskBottomImage) {
-                                                       $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask'];
-                                               }
-                                               //      Scaling:        ****
-                                               $tempScale = array();
-                                               $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
-                                               $command = $this->modifyImageMagickStripProfileParameters($command, $configuration);
-                                               $tmpStr = $gifBuilder->randomName();
-                                               //      m_mask
-                                               $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension;
-                                               $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(TRUE), $tempScale['m_mask'], $command . $negate);
-                                               //      m_bgImg
-                                               $tempScale['m_bgImg'] = $tmpStr . '_bgImg.' . trim($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_noloss']);
-                                               $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command);
-                                               //      m_bottomImg / m_bottomImg_mask
-                                               if ($maskBottomImage instanceof \TYPO3\CMS\Core\Resource\FileInterface && $maskBottomImageMask instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
-                                                       $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension;
-                                                       $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command);
-                                                       $tempScale['m_bottomImg_mask'] = $tmpStr . '_bottomImg_mask.' . $temporaryExtension;
-                                                       $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command . $negate);
-                                                       // BEGIN combining:
-                                                       // The image onto the background
-                                                       $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
-                                               }
-                                               // The image onto the background
-                                               $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName);
-                                               // Unlink the temp-images...
-                                               foreach ($tempScale as $file) {
-                                                       if (@is_file($file)) {
-                                                               unlink($file);
-                                                       }
-                                               }
-                                       }
-                               }
-                               // Finish off
-                               list($targetWidth, $targetHeight) = $gifBuilder->getImageDimensions($temporaryFileName);
-                       }
-                       // Temporary image was created
-                       if (file_exists($temporaryFileName)) {
-                               $updatedProperties = array('width' => $targetWidth, 'height' => $targetHeight);
-                               // ImageMagick did not have to do anything, as it is already there...
-                               if ($originalFileName !== $temporaryFileName) {
-                                       \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryFileName);
-                                       // Copy the temporary file to the processedFolder
-                                       // this is done here, as the driver can do this without worrying
-                                       // about existing ProcessedFile objects
-                                       // or permissions in the storage
-                                       // for "remote" storages this means "uploading" the file to the storage again
-                                       // for the virtual storage, it is merely a thing of "copying a file from typo3temp/ to typo3temp/_processed_"
-                                       $this->driver->addFile($temporaryFileName, $targetFolder, $targetFileName, $processedFile);
-                                       // Remove the temporary file as it's not needed anymore
-                                       \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile($temporaryFileName);
-                               } else {
-
-                               }
-                               $processedFile->updateProperties($updatedProperties);
-                               $processedFile->setProcessed(TRUE);
-                       }
-               }
+       protected function emitPreFileProcess(Resource\ProcessedFile $processedFile, Resource\FileInterface $file, $context, array $configuration = array()) {
+               $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PreFileProcess, array($this, $this->driver, $processedFile, $file, $context, $configuration));
        }
 
        /**
-        * Modifies the parameters for ImageMagick for stripping of profile information.
+        * Emits file post-processing signal.
         *
-        * @param string $parameters The parameters to be modified (if required)
-        * @param array $configuration The TypoScript configuration of [IMAGE].file
-        * @return string
+        * @param Resource\ProcessedFile $processedFile
+        * @param Resource\FileInterface $file
+        * @param $context
+        * @param array $configuration
         */
-       protected function modifyImageMagickStripProfileParameters($parameters, array $configuration) {
-               // Strips profile information of image to save some space:
-               if (isset($configuration['stripProfile'])) {
-                       if ($configuration['stripProfile']) {
-                               $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_stripProfileCommand'] . $parameters;
-                       } else {
-                               $parameters .= '###SkipStripProfile###';
-                       }
-               }
-               return $parameters;
+       protected function emitPostFileProcess(Resource\ProcessedFile $processedFile, Resource\FileInterface $file, $context, array $configuration = array()) {
+               $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Core\\Resource\\ResourceStorage', self::SIGNAL_PostFileProcess, array($this, $this->driver, $processedFile, $file, $context, $configuration));
        }
-
 }
 
 
index e86dc3e..49cff0b 100644 (file)
@@ -4,7 +4,7 @@ namespace TYPO3\CMS\Core\Resource\Service;
 /***************************************************************
  *  Copyright notice
  *
- *  (c) 2011 Andreas Wolf <andreas.wolf@ikt-werk.de>
+ *  (c) 2011 Andreas Wolf <andreas.wolf@typo3.org>
  *  All rights reserved
  *
  *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -30,7 +30,7 @@ namespace TYPO3\CMS\Core\Resource\Service;
 /**
  * Thumbnail service
  *
- * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
+ * @author Andreas Wolf <andreas.wolf@typo3.org>
  */
 class ImageProcessingService {
 
diff --git a/typo3/sysext/core/Tests/Unit/Resource/ProcessedFileRepositoryTest.php b/typo3/sysext/core/Tests/Unit/Resource/ProcessedFileRepositoryTest.php
new file mode 100644 (file)
index 0000000..3738931
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Resource;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012 Helmut Hummel <helmut.hummel@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+class ProcessedFileRepositoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @test
+        */
+       public function cleanUnavailableColumnsWorks() {
+               $fixture = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Resource\\ProcessedFileRepository', array('dummy'), array(), '', FALSE);
+               $databaseMock = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Database\\DatabaseConnection', array('admin_get_fields'));
+               $databaseMock->expects($this->once())->method('admin_get_fields')->will($this->returnValue(array('storage' => '', 'checksum' => '')));
+               $fixture->_set('databaseConnection', $databaseMock);
+
+               $actual = $fixture->_call('cleanUnavailableColumns', array('storage' => 'a', 'checksum' => 'b', 'key3' => 'c'));
+
+               $this->assertSame(array('storage' => 'a', 'checksum' => 'b'), $actual);
+       }
+
+}
index ea4a836..e531316 100644 (file)
@@ -144,6 +144,9 @@ class ResourceStorageTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCa
                $this->assertEquals($uri . '/', $this->fixture->getBaseUri());
        }
 
+       /**
+        * @return array
+        */
        public function capabilitiesDataProvider() {
                return array(
                        'only public' => array(
index d7a3afb..092ff84 100644 (file)
@@ -5084,7 +5084,7 @@ class ContentObjectRenderer {
                                                                3 => $processedFileObject->getPublicUrl(),
                                                                'origFile' => $fileObject->getPublicUrl(),
                                                                'origFile_mtime' => $fileObject->getModificationTime(),
-                                                               // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, line 100ff
+                                                               // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
                                                                // in order for the setup-array to create a unique filename hash.
                                                                'originalFile' => $fileObject,
                                                                'processedFile' => $processedFileObject,
@@ -7751,5 +7751,4 @@ class ContentObjectRenderer {
 
 }
 
-
 ?>
index 6308fa3..116e71b 100644 (file)
@@ -37,7 +37,7 @@
  */
 require_once 'init.php';
 // Make instance:
-$SOBE = t3lib_div::makeInstance('TYPO3_tcefile');
+$SOBE = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Controller\\File\\FileController');
 $SOBE->init();
 $SOBE->main();
 $SOBE->finish();