[SECURITY] Disallow insecure deserialization for l18n_diffsource
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Resource / FileCollector.php
1 <?php
2 namespace TYPO3\CMS\Frontend\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 Psr\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Core\LinkHandling\LinkService;
20 use TYPO3\CMS\Core\Resource\Exception;
21 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
22 use TYPO3\CMS\Core\Resource\FileCollectionRepository;
23 use TYPO3\CMS\Core\Resource\FileInterface;
24 use TYPO3\CMS\Core\Resource\FileRepository;
25 use TYPO3\CMS\Core\Resource\Folder;
26 use TYPO3\CMS\Core\Resource\ResourceFactory;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29 /**
30 * Object to collect files from various sources during runtime
31 * Sources can be file references, file collections or folders
32 *
33 * Use in FILES Content Object or for a Fluid Data Processor
34 *
35 * Is not persisted, use only in FE.
36 * @internal this is a internal TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
37 */
38 class FileCollector implements \Countable, LoggerAwareInterface
39 {
40 use LoggerAwareTrait;
41
42 /**
43 * The files
44 *
45 * @var array
46 */
47 protected $files = [];
48
49 /**
50 * The file repository
51 *
52 * @var \TYPO3\CMS\Core\Resource\FileRepository
53 */
54 protected $fileRepository;
55
56 /**
57 * The file collection repository
58 *
59 * @var \TYPO3\CMS\Core\Resource\FileCollectionRepository
60 */
61 protected $fileCollectionRepository;
62
63 /**
64 * The resource factory
65 *
66 * @var \TYPO3\CMS\Core\Resource\ResourceFactory
67 */
68 protected $resourceFactory;
69
70 /**
71 * Add files
72 *
73 * @param array $fileUids
74 */
75 public function addFiles(array $fileUids = [])
76 {
77 if (!empty($fileUids)) {
78 foreach ($fileUids as $fileUid) {
79 try {
80 $this->addFileObject($this->getResourceFactory()->getFileObject($fileUid));
81 } catch (Exception $e) {
82 $this->logger->warning(
83 'The file with uid "' . $fileUid
84 . '" could not be found and won\'t be included in frontend output',
85 ['exception' => $e]
86 );
87 }
88 }
89 }
90 }
91
92 /**
93 * Add files to the collection from a relation
94 *
95 * @param string $relationTable The table of the relation (e.g. tt_content or pages)
96 * @param string $relationField The field which holds the files (e.g. media or images)
97 * @param array $referenceRecord the record which is referencing the files
98 */
99 public function addFilesFromRelation($relationTable, $relationField, array $referenceRecord)
100 {
101 if (is_object($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']->sys_page)) {
102 $fileReferences = $this->getFileReferences($relationTable, $relationField, $referenceRecord);
103 } else {
104 $fileReferences = $this->getFileRepository()->findByRelation($relationTable, $relationField, $referenceRecord['uid']);
105 }
106
107 if (!empty($fileReferences)) {
108 $this->addFileObjects($fileReferences);
109 }
110 }
111
112 /**
113 * Add files from UIDs of a reference
114 *
115 * @param array $fileReferenceUids
116 */
117 public function addFileReferences(array $fileReferenceUids = [])
118 {
119 foreach ($fileReferenceUids as $fileReferenceUid) {
120 $fileObject = $this->getFileRepository()->findFileReferenceByUid($fileReferenceUid);
121 $this->addFileObject($fileObject);
122 }
123 }
124
125 /**
126 * Add files to the collection from multiple file collections
127 *
128 * @param array $fileCollectionUids The file collections uids
129 */
130 public function addFilesFromFileCollections(array $fileCollectionUids = [])
131 {
132 foreach ($fileCollectionUids as $fileCollectionUid) {
133 $this->addFilesFromFileCollection($fileCollectionUid);
134 }
135 }
136
137 /**
138 * Add files to the collection from one single file collection
139 *
140 * @param int $fileCollectionUid The file collections uid
141 */
142 public function addFilesFromFileCollection($fileCollectionUid = null)
143 {
144 if (!empty($fileCollectionUid)) {
145 try {
146 $fileCollection = $this->getFileCollectionRepository()->findByUid($fileCollectionUid);
147
148 if ($fileCollection instanceof \TYPO3\CMS\Core\Resource\Collection\AbstractFileCollection) {
149 $fileCollection->loadContents();
150 $files = $fileCollection->getItems();
151
152 $this->addFileObjects($files);
153 }
154 } catch (Exception $e) {
155 $this->logger->warning(
156 'The file-collection with uid "' . $fileCollectionUid
157 . '" could not be found or contents could not be loaded and won\'t be included in frontend output.',
158 ['exception' => $e]
159 );
160 }
161 }
162 }
163
164 /**
165 * Add files to the collection from multiple folders
166 *
167 * @param array $folderIdentifiers The folder identifiers
168 * @param bool $recursive Add files recursive from given folders
169 */
170 public function addFilesFromFolders(array $folderIdentifiers = [], $recursive = false)
171 {
172 foreach ($folderIdentifiers as $folderIdentifier) {
173 $this->addFilesFromFolder($folderIdentifier, $recursive);
174 }
175 }
176
177 /**
178 * Add files to the collection from one single folder
179 *
180 * @param string $folderIdentifier The folder identifier
181 * @param bool $recursive Add files recursive from given folders
182 */
183 public function addFilesFromFolder($folderIdentifier, $recursive = false)
184 {
185 if ($folderIdentifier) {
186 try {
187 if (strpos($folderIdentifier, 't3://folder') === 0) {
188 // a t3://folder link to a folder in FAL
189 $linkService = GeneralUtility::makeInstance(LinkService::class);
190 $data = $linkService->resolveByStringRepresentation($folderIdentifier);
191 $folder = $data['folder'];
192 } else {
193 $folder = $this->getResourceFactory()->getFolderObjectFromCombinedIdentifier($folderIdentifier);
194 }
195 if ($folder instanceof Folder) {
196 $files = $folder->getFiles(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive);
197 $this->addFileObjects(array_values($files));
198 }
199 } catch (Exception $e) {
200 $this->logger->warning(
201 'The folder with identifier "' . $folderIdentifier
202 . '" could not be found and won\'t be included in frontend output',
203 ['exception' => $e]
204 );
205 }
206 }
207 }
208
209 /**
210 * Sort the file objects based on a property
211 *
212 * @param string $sortingProperty The sorting property
213 * @param string $sortingOrder can be ascending or descending or "random"
214 */
215 public function sort($sortingProperty = '', $sortingOrder = 'ascending')
216 {
217 if ($sortingProperty !== '' && count($this->files) > 1) {
218 @usort(
219 $this->files,
220 function (
221 FileInterface $a,
222 FileInterface $b
223 ) use ($sortingProperty) {
224 if ($a->hasProperty($sortingProperty) && $b->hasProperty($sortingProperty)) {
225 return strnatcasecmp($a->getProperty($sortingProperty), $b->getProperty($sortingProperty));
226 }
227 return 0;
228 }
229 );
230
231 switch (strtolower($sortingOrder)) {
232 case 'descending':
233 case 'desc':
234 $this->files = array_reverse($this->files);
235 break;
236 case 'random':
237 case 'rand':
238 shuffle($this->files);
239 break;
240 }
241 }
242 }
243
244 /**
245 * Add a file object to the collection
246 *
247 * @param FileInterface $file The file object
248 */
249 public function addFileObject(FileInterface $file)
250 {
251 $this->files[] = $file;
252 }
253
254 /**
255 * Add multiple file objects to the collection
256 *
257 * @param FileInterface[] $files The file objects
258 */
259 public function addFileObjects($files)
260 {
261 $this->files = array_merge($this->files, $files);
262 }
263
264 /**
265 * Final getter method to fetch the accumulated data
266 *
267 * @return array
268 */
269 public function getFiles()
270 {
271 return $this->files;
272 }
273
274 /**
275 * @return int
276 */
277 public function count()
278 {
279 return count($this->files);
280 }
281
282 /**
283 * Gets file references for a given record field.
284 *
285 * @param string $tableName Name of the table
286 * @param string $fieldName Name of the field
287 * @param array $element The parent element referencing to files
288 * @return array
289 */
290 protected function getFileReferences($tableName, $fieldName, array $element): array
291 {
292 /** @var FileRepository $fileRepository */
293 $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
294 $currentId = !empty($element['uid']) ? $element['uid'] : 0;
295
296 // Fetch the references of the default element
297 try {
298 $references = $fileRepository->findByRelation($tableName, $fieldName, $currentId);
299 } catch (FileDoesNotExistException $e) {
300 /**
301 * We just catch the exception here
302 * Reasoning: There is nothing an editor or even admin could do
303 */
304 return [];
305 } catch (\InvalidArgumentException $e) {
306 /**
307 * The storage does not exist anymore
308 * Log the exception message for admins as they maybe can restore the storage
309 */
310 $logMessage = $e->getMessage() . ' (table: "' . $tableName . '", fieldName: "' . $fieldName . '", currentId: ' . $currentId . ')';
311 $this->logger->error($logMessage, ['exception' => $e]);
312 return [];
313 }
314
315 $localizedId = null;
316 if (isset($element['_LOCALIZED_UID'])) {
317 $localizedId = $element['_LOCALIZED_UID'];
318 } elseif (isset($element['_PAGES_OVERLAY_UID'])) {
319 $localizedId = $element['_PAGES_OVERLAY_UID'];
320 }
321
322 $isTableLocalizable = (
323 !empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
324 && !empty($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
325 );
326 if ($isTableLocalizable && $localizedId !== null) {
327 $localizedReferences = $fileRepository->findByRelation($tableName, $fieldName, $localizedId);
328 $references = $localizedReferences;
329 }
330
331 return $references;
332 }
333
334 /**
335 * @return ResourceFactory
336 */
337 protected function getResourceFactory()
338 {
339 if ($this->resourceFactory === null) {
340 $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
341 }
342 return $this->resourceFactory;
343 }
344
345 /**
346 * @return FileCollectionRepository
347 */
348 protected function getFileCollectionRepository()
349 {
350 if ($this->fileCollectionRepository === null) {
351 $this->fileCollectionRepository = GeneralUtility::makeInstance(FileCollectionRepository::class);
352 }
353 return $this->fileCollectionRepository;
354 }
355
356 /**
357 * @return FileRepository
358 */
359 protected function getFileRepository()
360 {
361 if ($this->fileRepository === null) {
362 $this->fileRepository = GeneralUtility::makeInstance(FileRepository::class);
363 }
364 return $this->fileRepository;
365 }
366 }