[FEATURE] Add "processed files" cleanup tool to Install Tool
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / ProcessedFileRepository.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource;
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
17 use TYPO3\CMS\Core\Log\LogManager;
18 use TYPO3\CMS\Core\Utility;
19
20 /**
21 * Repository for accessing files
22 * it also serves as the public API for the indexing part of files in general
23 *
24 * @author Benjamin Mack <benni@typo3.org>
25 * @author Ingmar Schlecht <ingmar@typo3.org>
26 */
27 class ProcessedFileRepository extends AbstractRepository {
28
29 /**
30 * The main object type of this class. In some cases (fileReference) this
31 * repository can also return FileReference objects, implementing the
32 * common FileInterface.
33 *
34 * @var string
35 */
36 protected $objectType = ProcessedFile::class;
37
38 /**
39 * Main File object storage table. Note that this repository also works on
40 * the sys_file_reference table when returning FileReference objects.
41 *
42 * @var string
43 */
44 protected $table = 'sys_file_processedfile';
45
46 /**
47 * @var ResourceFactory
48 */
49 protected $resourceFactory;
50
51 /**
52 * @var \TYPO3\CMS\Core\Database\DatabaseConnection
53 */
54 protected $databaseConnection;
55
56 /**
57 * Creates this object.
58 */
59 public function __construct() {
60 $this->resourceFactory = Utility\GeneralUtility::makeInstance(ResourceFactory::class);
61 $this->databaseConnection = $GLOBALS['TYPO3_DB'];
62 }
63
64 /**
65 * Creates a ProcessedFile object from a file object and a processing configuration
66 *
67 * @param FileInterface $originalFile
68 * @param string $taskType
69 * @param array $configuration
70 * @return ProcessedFile
71 */
72 public function createNewProcessedFileObject(FileInterface $originalFile, $taskType, array $configuration) {
73 return Utility\GeneralUtility::makeInstance(
74 $this->objectType,
75 $originalFile,
76 $taskType,
77 $configuration
78 );
79 }
80
81 /**
82 * @param array $databaseRow
83 * @return ProcessedFile
84 */
85 protected function createDomainObject(array $databaseRow) {
86 $originalFile = $this->resourceFactory->getFileObject((int)$databaseRow['original']);
87 $originalFile->setStorage($this->resourceFactory->getStorageObject($originalFile->getProperty('storage')));
88 $taskType = $databaseRow['task_type'];
89 $configuration = unserialize($databaseRow['configuration']);
90
91 return Utility\GeneralUtility::makeInstance(
92 $this->objectType,
93 $originalFile,
94 $taskType,
95 $configuration,
96 $databaseRow
97 );
98 }
99
100 /**
101 * @param ResourceStorage $storage
102 * @param string $identifier
103 *
104 * @return null|ProcessedFile
105 */
106 public function findByStorageAndIdentifier(ResourceStorage $storage, $identifier) {
107 $processedFileObject = NULL;
108 if ($storage->hasFile($identifier)) {
109 $databaseRow = $this->databaseConnection->exec_SELECTgetSingleRow(
110 '*',
111 $this->table,
112 'storage = ' . (int)$storage->getUid() .
113 ' AND identifier = ' . $this->databaseConnection->fullQuoteStr($identifier, $this->table)
114 );
115 if ($databaseRow) {
116 $processedFileObject = $this->createDomainObject($databaseRow);
117 }
118 }
119 return $processedFileObject;
120 }
121 /**
122 * Adds a processedfile object in the database
123 *
124 * @param ProcessedFile $processedFile
125 * @return void
126 */
127 public function add($processedFile) {
128 if ($processedFile->isPersisted()) {
129 $this->update($processedFile);
130 } else {
131 $insertFields = $processedFile->toArray();
132 $insertFields['crdate'] = $insertFields['tstamp'] = time();
133 $insertFields = $this->cleanUnavailableColumns($insertFields);
134 $this->databaseConnection->exec_INSERTquery($this->table, $insertFields);
135 $uid = $this->databaseConnection->sql_insert_id();
136 $processedFile->updateProperties(array('uid' => $uid));
137 }
138 }
139
140 /**
141 * Updates an existing file object in the database
142 *
143 * @param ProcessedFile $processedFile
144 * @return void
145 */
146 public function update($processedFile) {
147 if ($processedFile->isPersisted()) {
148 $uid = (int)$processedFile->getUid();
149 $updateFields = $this->cleanUnavailableColumns($processedFile->toArray());
150 $updateFields['tstamp'] = time();
151 $this->databaseConnection->exec_UPDATEquery($this->table, 'uid=' . (int)$uid, $updateFields);
152 }
153 }
154
155 /**
156 * @param \TYPO3\CMS\Core\Resource\File|\TYPO3\CMS\Core\Resource\FileInterface $file
157 * @param string $taskType The task that should be executed on the file
158 * @param array $configuration
159 *
160 * @return ProcessedFile
161 */
162 public function findOneByOriginalFileAndTaskTypeAndConfiguration(FileInterface $file, $taskType, array $configuration) {
163 $databaseRow = $this->databaseConnection->exec_SELECTgetSingleRow(
164 '*',
165 $this->table,
166 'original=' . (int)$file->getUid() .
167 ' AND task_type=' . $this->databaseConnection->fullQuoteStr($taskType, $this->table) .
168 ' AND configurationsha1=' . $this->databaseConnection->fullQuoteStr(sha1(serialize($configuration)), $this->table)
169 );
170
171 if (is_array($databaseRow)) {
172 $processedFile = $this->createDomainObject($databaseRow);
173 } else {
174 $processedFile = $this->createNewProcessedFileObject($file, $taskType, $configuration);
175 }
176 return $processedFile;
177 }
178
179 /**
180 * @param FileInterface $file
181 * @return ProcessedFile[]
182 * @throws \InvalidArgumentException
183 */
184 public function findAllByOriginalFile(FileInterface $file) {
185 if (!$file instanceof File) {
186 throw new \InvalidArgumentException('Parameter is no File object but got type "'
187 . (is_object($file) ? get_class($file) : gettype($file)) . '"', 1382006142);
188 }
189 $whereClause = 'original=' . (int)$file->getUid();
190 $rows = $this->databaseConnection->exec_SELECTgetRows('*', $this->table, $whereClause);
191
192 $itemList = array();
193 if ($rows !== NULL) {
194 foreach ($rows as $row) {
195 $itemList[] = $this->createDomainObject($row);
196 }
197 }
198 return $itemList;
199 }
200
201 /**
202 * Removes all processed files and also deletes the associated physical files
203 *
204 * @param int|NULL $storageUid If not NULL, only the processed files of the given storage are removed
205 * @return int Number of failed deletions
206 */
207 public function removeAll($storageUid = NULL) {
208 $res = $this->databaseConnection->exec_SELECTquery('*', $this->table, 'identifier <> \'\'');
209 $logger = $this->getLogger();
210 $errorCount = 0;
211 while ($row = $this->databaseConnection->sql_fetch_assoc($res)) {
212 if ($storageUid && (int)$storageUid !== (int)$row['storage']) {
213 continue;
214 }
215 try {
216 $file = $this->createDomainObject($row);
217 $file->getStorage()->setEvaluatePermissions(FALSE);
218 $file->delete(TRUE);
219 } catch (\Exception $e) {
220 $logger->error(
221 'Failed to delete file "' . $row['identifier'] . '" in storage uid ' . $row['storage'] . '.',
222 array(
223 'exception' => $e
224 )
225 );
226 ++$errorCount;
227 }
228 }
229
230 $this->databaseConnection->exec_TRUNCATEquery($this->table);
231
232 return $errorCount;
233 }
234
235
236 /**
237 * Removes all array keys which cannot be persisted
238 *
239 * @param array $data
240 *
241 * @return array
242 */
243 protected function cleanUnavailableColumns(array $data) {
244 return array_intersect_key($data, $this->databaseConnection->admin_get_fields($this->table));
245 }
246
247 /**
248 * @return \TYPO3\CMS\Core\Log\Logger
249 */
250 protected function getLogger() {
251 return Utility\GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
252 }
253
254 }