1<?php
2namespace TYPO3\CMS\Core\Resource\Service;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use TYPO3\CMS\Core\Resource;
18use TYPO3\CMS\Core\Utility\GeneralUtility;
19use TYPO3\CMS\Core\Utility\MathUtility;
20use TYPO3\CMS\Extbase\Object\ObjectManager;
21use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
22
23/**
24 * File processing service
25 */
26class FileProcessingService
27{
28    /**
29     * @var Resource\ResourceStorage
30     */
31    protected $storage;
32
33    /**
34     * @var Resource\Driver\DriverInterface
35     */
36    protected $driver;
37
38    /**
39     * @var Dispatcher
40     */
41    protected $signalSlotDispatcher;
42
43    const SIGNAL_PreFileProcess = 'preFileProcess';
44    const SIGNAL_PostFileProcess = 'postFileProcess';
45
46    /**
47     * Creates this object.
48     *
49     * @param Resource\ResourceStorage $storage
50     * @param Resource\Driver\DriverInterface $driver
51     */
52    public function __construct(Resource\ResourceStorage $storage, Resource\Driver\DriverInterface $driver)
53    {
54        $this->storage = $storage;
55        $this->driver = $driver;
56    }
57
58    /**
59     * Processes a file
60     *
61     * @param Resource\FileInterface $fileObject The file object
62     * @param Resource\ResourceStorage $targetStorage The storage to store the processed file in
63     * @param string $taskType
64     * @param array $configuration
65     *
66     * @return Resource\ProcessedFile
67     * @throws \InvalidArgumentException
68     */
69    public function processFile(Resource\FileInterface $fileObject, Resource\ResourceStorage $targetStorage, $taskType, $configuration)
70    {
71        // Enforce default configuration for preview processing here,
72        // to be sure we find already processed files below,
73        // which we wouldn't if we would change the configuration later, as configuration is part of the lookup.
74        if ($taskType === Resource\ProcessedFile::CONTEXT_IMAGEPREVIEW) {
75            $configuration = Resource\Processing\LocalPreviewHelper::preProcessConfiguration($configuration);
76        }
77        // Ensure that the processing configuration which is part of the hash sum is properly cast, so
78        // unnecessary duplicate images are not produced, see #80942
79        foreach ($configuration as &$value) {
80            if (MathUtility::canBeInterpretedAsInteger($value)) {
81                $value = (int)$value;
82            }
83        }
84
85        /** @var Resource\ProcessedFileRepository $processedFileRepository */
86        $processedFileRepository = GeneralUtility::makeInstance(Resource\ProcessedFileRepository::class);
87
88        $processedFile = $processedFileRepository->findOneByOriginalFileAndTaskTypeAndConfiguration($fileObject, $taskType, $configuration);
89
90        // set the storage of the processed file
91        // Pre-process the file
92        $this->emitPreFileProcessSignal($processedFile, $fileObject, $taskType, $configuration);
93
94        // Only handle the file if it is not processed yet
95        // (maybe modified or already processed by a signal)
96        // or (in case of preview images) already in the DB/in the processing folder
97        if (!$processedFile->isProcessed()) {
98            $this->process($processedFile, $targetStorage);
99        }
100
101        // Post-process (enrich) the file
102        $this->emitPostFileProcessSignal($processedFile, $fileObject, $taskType, $configuration);
103
104        return $processedFile;
105    }
106
107    /**
108     * Processes the file
109     *
110     * @param Resource\ProcessedFile $processedFile
111     * @param Resource\ResourceStorage $targetStorage The storage to put the processed file into
112     */
113    protected function process(Resource\ProcessedFile $processedFile, Resource\ResourceStorage $targetStorage)
114    {
115        // We only have to trigger the file processing if the file either is new, does not exist or the
116        // original file has changed since the last processing run (the last case has to trigger a reprocessing
117        // even if the original file was used until now)
118        if ($processedFile->isNew() || (!$processedFile->usesOriginalFile() && !$processedFile->exists()) ||
119            $processedFile->isOutdated()) {
120            $task = $processedFile->getTask();
121            /** @var Resource\Processing\LocalImageProcessor $processor */
122            $processor = GeneralUtility::makeInstance(Resource\Processing\LocalImageProcessor::class);
123            $processor->processTask($task);
124
125            if ($task->isExecuted() && $task->isSuccessful() && $processedFile->isProcessed()) {
126                /** @var Resource\ProcessedFileRepository $processedFileRepository */
127                $processedFileRepository = GeneralUtility::makeInstance(Resource\ProcessedFileRepository::class);
128                $processedFileRepository->add($processedFile);
129            }
130        }
131    }
132
133    /**
134     * Get the SignalSlot dispatcher
135     *
136     * @return Dispatcher
137     */
138    protected function getSignalSlotDispatcher()
139    {
140        if (!isset($this->signalSlotDispatcher)) {
141            $this->signalSlotDispatcher = GeneralUtility::makeInstance(ObjectManager::class)->get(Dispatcher::class);
142        }
143        return $this->signalSlotDispatcher;
144    }
145
146    /**
147     * Emits file pre-processing signal.
148     *
149     * @param Resource\ProcessedFile $processedFile
150     * @param Resource\FileInterface $file
151     * @param string $context
152     * @param array $configuration
153     */
154    protected function emitPreFileProcessSignal(Resource\ProcessedFile $processedFile, Resource\FileInterface $file, $context, array $configuration = [])
155    {
156        $this->getSignalSlotDispatcher()->dispatch(
157            Resource\ResourceStorage::class,
158            self::SIGNAL_PreFileProcess,
159            [$this, $this->driver, $processedFile, $file, $context, $configuration]
160        );
161    }
162
163    /**
164     * Emits file post-processing signal.
165     *
166     * @param Resource\ProcessedFile $processedFile
167     * @param Resource\FileInterface $file
168     * @param $context
169     * @param array $configuration
170     */
171    protected function emitPostFileProcessSignal(Resource\ProcessedFile $processedFile, Resource\FileInterface $file, $context, array $configuration = [])
172    {
173        $this->getSignalSlotDispatcher()->dispatch(
174            Resource\ResourceStorage::class,
175            self::SIGNAL_PostFileProcess,
176            [$this, $this->driver, $processedFile, $file, $context, $configuration]
177        );
178    }
179}
180