5c4765f52abda31998defa26903575f3c6ce1923
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / DataProcessing / GalleryProcessor.php
1 <?php
2 namespace TYPO3\CMS\Frontend\DataProcessing;
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\Frontend\ContentObject\ContentObjectRenderer;
18 use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
19 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
20
21 /**
22 * This data processor will calculate rows, columns and dimensions for a gallery
23 * based on several settings and can be used for f.i. the CType "textmedia"
24 *
25 * The output will be an array which contains the rows and columns,
26 * including the file references and the calculated width and height for each media element,
27 * but also some more information of the gallery, like position, width and counters
28 *
29 * Example TypoScript configuration:
30 *
31 * 10 = TYPO3\CMS\Frontend\DataProcessing\GalleryProcessor
32 * 10 {
33 * filesProcessedDataKey = files
34 * mediaOrientation.field = imageorient
35 * numberOfColumns.field = imagecols
36 * equalMediaHeight.field = imageheight
37 * equalMediaWidth.field = imagewidth
38 * columnSpacing = 0
39 * borderEnabled.field = imageborder
40 * borderPadding = 0
41 * borderWidth = 0
42 * maxGalleryWidth = {$styles.content.imgtext.maxW}
43 * maxGalleryWidthInText = {$styles.content.imgtext.maxWInText}
44 * as = gallery
45 * }
46 *
47 * Output example:
48 *
49 * gallery {
50 * position {
51 * horizontal = center
52 * vertical = above
53 * noWrap = FALSE
54 * }
55 * width = 600
56 * count {
57 * files = 2
58 * columns = 1
59 * rows = 2
60 * }
61 * rows {
62 * 1 {
63 * columns {
64 * 1 {
65 * media = TYPO3\CMS\Core\Resource\FileReference
66 * dimensions {
67 * width = 600
68 * height = 400
69 * }
70 * }
71 * }
72 * }
73 * 2 {
74 * columns {
75 * 1 {
76 * media = TYPO3\CMS\Core\Resource\FileReference
77 * dimensions {
78 * width = 600
79 * height = 400
80 * }
81 * }
82 * }
83 * }
84 * }
85 * columnSpacing = 0
86 * border {
87 * enabled = FALSE
88 * width = 0
89 * padding = 0
90 * }
91 * }
92 *
93 */
94 class GalleryProcessor implements DataProcessorInterface {
95
96 /**
97 * The content object renderer
98 *
99 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
100 */
101 protected $contentObjectRenderer;
102
103 /**
104 * The processor configuration
105 *
106 * @var array
107 */
108 protected $processorConfiguration;
109
110 /**
111 * Matching the tt_content field towards the imageOrient option
112 *
113 * @var array
114 */
115 protected $availableGalleryPositions = [
116 'horizontal' => [
117 'center' => [0, 8],
118 'right' => [1, 9, 17, 25],
119 'left' => [2, 10, 18, 26]
120 ],
121 'vertical' => [
122 'above' => [0, 1, 2],
123 'intext' => [17, 18, 25, 26],
124 'below' => [8, 9, 10]
125 ]
126 ];
127
128 /**
129 * Storage for processed data
130 *
131 * @var array
132 */
133 protected $galleryData = [
134 'position' => [
135 'horizontal' => '',
136 'vertical' => '',
137 'noWrap' => FALSE
138 ],
139 'width' => 0,
140 'count' => [
141 'files' => 0,
142 'columns' => 0,
143 'rows' => 0,
144 ],
145 'columnSpacing' => 0,
146 'border' => [
147 'enabled' => FALSE,
148 'width' => 0,
149 'padding' => 0,
150 ],
151 'rows' => []
152 ];
153
154 /**
155 * @var int
156 */
157 protected $numberOfColumns;
158
159 /**
160 * @var int
161 */
162 protected $mediaOrientation;
163
164 /**
165 * @var int
166 */
167 protected $maxGalleryWidth;
168
169 /**
170 * @var int
171 */
172 protected $maxGalleryWidthInText;
173
174 /**
175 * @var int
176 */
177 protected $equalMediaHeight;
178
179 /**
180 * @var int
181 */
182 protected $equalMediaWidth;
183
184 /**
185 * @var int
186 */
187 protected $columnSpacing;
188
189 /**
190 * @var bool
191 */
192 protected $borderEnabled;
193
194 /**
195 * @var int
196 */
197 protected $borderWidth;
198
199 /**
200 * @var int
201 */
202 protected $borderPadding;
203
204 /**
205 * The (filtered) media files to be used in the gallery
206 *
207 * @var fileObjects[]
208 */
209 protected $fileObjects = [];
210
211 /**
212 * The calculated dimensions for each media element
213 *
214 * @var array
215 */
216 protected $mediaDimensions = [];
217
218 /**
219 * Process data for a gallery, for instance the CType "textmedia"
220 *
221 * @param ContentObjectRenderer $cObj The content object renderer, which contains data of the content element
222 * @param array $contentObjectConfiguration The configuration of Content Object
223 * @param array $processorConfiguration The configuration of this processor
224 * @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
225 * @return array the processed data as key/value store
226 * @throws ContentRenderingException
227 */
228 public function process(
229 ContentObjectRenderer $cObj,
230 array $contentObjectConfiguration,
231 array $processorConfiguration,
232 array $processedData
233 ) {
234 if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
235 return $processedData;
236 }
237
238 $this->contentObjectRenderer = $cObj;
239 $this->processorConfiguration = $processorConfiguration;
240
241 $filesProcessedDataKey = (string)$cObj->stdWrapValue(
242 'filesProcessedDataKey',
243 $processorConfiguration,
244 'files'
245 );
246 if (isset($processedData[$filesProcessedDataKey]) && is_array($processedData[$filesProcessedDataKey])) {
247 $this->fileObjects = $processedData[$filesProcessedDataKey];
248 $this->galleryData['count']['files'] = count($this->fileObjects);
249 } else {
250 throw new ContentRenderingException('No files found for key ' . $filesProcessedDataKey . ' in $processedData.', 1436809789);
251 }
252
253 $this->numberOfColumns = (int)$this->getConfigurationValue('numberOfColumns', 'imagecols');
254 $this->mediaOrientation = (int)$this->getConfigurationValue('mediaOrientation', 'imageorient');
255 $this->maxGalleryWidth = (int)$this->getConfigurationValue('maxGalleryWidth') ?: 600;
256 $this->maxGalleryWidthInText = (int)$this->getConfigurationValue('maxGalleryWidthInText') ?: 300;
257 $this->equalMediaHeight = (int)$this->getConfigurationValue('equalMediaHeight', 'imageheight');
258 $this->equalMediaWidth = (int)$this->getConfigurationValue('equalMediaWidth', 'imagewidth');
259 $this->columnSpacing = (int)$this->getConfigurationValue('columnSpacing');
260 $this->borderEnabled = (bool)$this->getConfigurationValue('borderEnabled', 'imageborder');
261 $this->borderWidth = (int)$this->getConfigurationValue('borderWidth');
262 $this->borderPadding = (int)$this->getConfigurationValue('borderPadding');
263
264 $this->determineGalleryPosition();
265 $this->determineMaximumGalleryWidth();
266
267 $this->calculateRowsAndColumns();
268 $this->calculateMediaWidthsAndHeights();
269
270 $this->prepareGalleryData();
271
272 $targetFieldName = (string)$cObj->stdWrapValue(
273 'as',
274 $processorConfiguration,
275 'gallery'
276 );
277
278 $processedData[$targetFieldName] = $this->galleryData;
279
280 return $processedData;
281 }
282
283 /**
284 * Get configuration value from processorConfiguration
285 * with when $dataArrayKey fallback to value from cObj->data array
286 *
287 * @param string $key
288 * @param string|NULL $dataArrayKey
289 * @return string
290 */
291 protected function getConfigurationValue($key, $dataArrayKey = NULL) {
292 $defaultValue = '';
293 if ($dataArrayKey && isset($this->contentObjectRenderer->data[$dataArrayKey])) {
294 $defaultValue = $this->contentObjectRenderer->data[$dataArrayKey];
295 }
296 return $this->contentObjectRenderer->stdWrapValue(
297 $key,
298 $this->processorConfiguration,
299 $defaultValue
300 );
301 }
302
303 /**
304 * Define the gallery position
305 *
306 * Gallery has a horizontal and a vertical position towards the text
307 * and a possible wrapping of the text around the gallery.
308 *
309 * @return void
310 */
311 protected function determineGalleryPosition() {
312 foreach ($this->availableGalleryPositions as $positionDirectionKey => $positionDirectionValue) {
313 foreach ($positionDirectionValue as $positionKey => $positionArray) {
314 if (in_array($this->mediaOrientation, $positionArray, TRUE)) {
315 $this->galleryData['position'][$positionDirectionKey] = $positionKey;
316 }
317 }
318 }
319
320 if ($this->mediaOrientation === 25 || $this->mediaOrientation === 26) {
321 $this->galleryData['position']['noWrap'] = TRUE;
322 }
323 }
324
325 /**
326 * Get the gallery width based on vertical position
327 *
328 * @return void
329 */
330 protected function determineMaximumGalleryWidth() {
331 if ($this->galleryData['position']['vertical'] === 'intext') {
332 $this->galleryData['width'] = $this->maxGalleryWidthInText;
333 } else {
334 $this->galleryData['width'] = $this->maxGalleryWidth;
335 }
336 }
337
338 /**
339 * Calculate the amount of rows and columns
340 *
341 * @return void
342 */
343 protected function calculateRowsAndColumns() {
344
345 // If no columns defined, set it to 1
346 $columns = max((int)$this->numberOfColumns, 1);
347
348 // When more columns than media elements, set the columns to the amount of media elements
349 if ($columns > $this->galleryData['count']['files']) {
350 $columns = $this->galleryData['count']['files'];
351 }
352
353 if ($columns === 0) {
354 $columns = 1;
355 }
356
357 // Calculate the rows from the amount of files and the columns
358 $rows = ceil($this->galleryData['count']['files'] / $columns);
359
360 $this->galleryData['count']['columns'] = $columns;
361 $this->galleryData['count']['rows'] = (int)$rows;
362 }
363
364 /**
365 * Calculate the width/height of the media elements
366 *
367 * Based on the width of the gallery, defined equal width or height by a user, the spacing between columns and
368 * the use of a border, defined by user, where the border width and padding are taken into account
369 *
370 * File objects MUST already be filtered. They need a height and width to be shown in the gallery
371 *
372 * @return void
373 */
374 protected function calculateMediaWidthsAndHeights() {
375
376 $columnSpacingTotal = ($this->galleryData['count']['columns'] - 1) * $this->columnSpacing;
377
378 $galleryWidthMinusBorderAndSpacing = max($this->galleryData['width'] - $columnSpacingTotal, 1);
379
380 if ($this->borderEnabled) {
381 $borderPaddingTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderPadding;
382 $borderWidthTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderWidth;
383 $galleryWidthMinusBorderAndSpacing = $galleryWidthMinusBorderAndSpacing - $borderPaddingTotal - $borderWidthTotal;
384 }
385
386 // User entered a predefined height
387 if ($this->equalMediaHeight) {
388 $mediaScalingCorrection = 1;
389 $maximumRowWidth = 0;
390
391 // Calculate the scaling correction when the total of media elements is wider than the gallery width
392 for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
393 $totalRowWidth = 0;
394 for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
395 $fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
396 if ($fileKey > $this->galleryData['count']['files'] - 1) {
397 break 2;
398 }
399 $currentMediaScaling = $this->equalMediaHeight / max($this->fileObjects[$fileKey]->getProperty('height'), 1);
400 $totalRowWidth += $this->fileObjects[$fileKey]->getProperty('width') * $currentMediaScaling;
401 }
402 $maximumRowWidth = max($totalRowWidth, $maximumRowWidth);
403 $mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
404 $mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
405 }
406
407 // Set the corrected dimensions for each media element
408 foreach ($this->fileObjects as $key => $fileObject) {
409 $mediaHeight = floor($this->equalMediaHeight / $mediaScalingCorrection);
410 $mediaWidth = floor(
411 $fileObject->getProperty('width') * ($mediaHeight / max($fileObject->getProperty('height'), 1))
412 );
413 $this->mediaDimensions[$key] = [
414 'width' => $mediaWidth,
415 'height' => $mediaHeight
416 ];
417 }
418
419 // Recalculate gallery width
420 $this->galleryData['width'] = floor($maximumRowWidth / $mediaScalingCorrection);
421
422 // User entered a predefined width
423 } elseif ($this->equalMediaWidth) {
424 $mediaScalingCorrection = 1;
425
426 // Calculate the scaling correction when the total of media elements is wider than the gallery width
427 $totalRowWidth = $this->galleryData['count']['columns'] * $this->equalMediaWidth;
428 $mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
429 $mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
430
431 // Set the corrected dimensions for each media element
432 foreach ($this->fileObjects as $key => $fileObject) {
433 $mediaWidth = floor($this->equalMediaWidth / $mediaScalingCorrection);
434 $mediaHeight = floor(
435 $fileObject->getProperty('height') * ($mediaWidth / max($fileObject->getProperty('width'), 1))
436 );
437 $this->mediaDimensions[$key] = [
438 'width' => $mediaWidth,
439 'height' => $mediaHeight
440 ];
441 }
442
443 // Recalculate gallery width
444 $this->galleryData['width'] = floor($totalRowWidth / $mediaScalingCorrection);
445
446 // Automatic setting of width and height
447 } else {
448 $maxMediaWidth = (int)($galleryWidthMinusBorderAndSpacing / $this->galleryData['count']['columns']);
449
450 foreach ($this->fileObjects as $key => $fileObject) {
451 $mediaWidth = min($maxMediaWidth, $fileObject->getProperty('width'));
452 $mediaHeight = floor(
453 $fileObject->getProperty('height') * ($mediaWidth / max($fileObject->getProperty('width'), 1))
454 );
455 $this->mediaDimensions[$key] = [
456 'width' => $mediaWidth,
457 'height' => $mediaHeight
458 ];
459 }
460 }
461 }
462
463 /**
464 * Prepare the gallery data
465 *
466 * Make an array for rows, columns and configuration
467 *
468 * @return void
469 */
470 protected function prepareGalleryData() {
471 for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
472
473 for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
474
475 $fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
476
477 $this->galleryData['rows'][$row]['columns'][$column] = [
478 'media' => $this->fileObjects[$fileKey],
479 'dimensions' => [
480 'width' => $this->mediaDimensions[$fileKey]['width'],
481 'height' => $this->mediaDimensions[$fileKey]['height']
482 ]
483 ];
484 }
485 }
486
487 $this->galleryData['columnSpacing'] = $this->columnSpacing;
488 $this->galleryData['border']['enabled'] = $this->borderEnabled;
489 $this->galleryData['border']['width'] = $this->borderWidth;
490 $this->galleryData['border']['padding'] = $this->borderPadding;
491 }
492 }