6696d10a0d96a8bb7c0aee68541cc9b268efd744
[Packages/TYPO3.CMS.git] / typo3 / sysext / css_styled_content / Classes / Controller / CssStyledContentController.php
1 <?php
2 namespace TYPO3\CMS\CssStyledContent\Controller;
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\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\MathUtility;
19
20 /**
21 * Plugin class - instantiated from TypoScript.
22 * Rendering some content elements from tt_content table.
23 *
24 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
25 */
26 class CssStyledContentController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
27
28 /**
29 * Same as class name
30 *
31 * @var string
32 */
33 public $prefixId = 'tx_cssstyledcontent_pi1';
34
35 /**
36 * Path to this script relative to the extension dir.
37 *
38 * @var string
39 */
40 public $scriptRelPath = 'Classes/Controller/CssStyledContentController.php';
41
42 /**
43 * The extension key
44 *
45 * @var string
46 */
47 public $extKey = 'css_styled_content';
48
49 /**
50 * @var array
51 */
52 public $conf = array();
53
54 /***********************************
55 * Rendering of Content Elements:
56 ***********************************/
57
58 /**
59 * Rendering the "Bulletlist" type content element, called from TypoScript (tt_content.bullets.20)
60 *
61 * @param string $content Content input. Not used, ignore.
62 * @param array $conf TypoScript configuration
63 * @return string HTML output.
64 */
65 public function render_bullets($content, $conf) {
66 // Look for hook before running default code for function
67 if ($hookObj = $this->hookRequest('render_bullets')) {
68 return $hookObj->render_bullets($content, $conf);
69 } else {
70 // Get bodytext field content, returning blank if empty:
71 $field = isset($conf['field']) && trim($conf['field']) ? trim($conf['field']) : 'bodytext';
72 $content = trim($this->cObj->data[$field]);
73 if ($content === '') {
74 return '';
75 }
76 // Split into single lines:
77 $lines = GeneralUtility::trimExplode(LF, $content);
78 foreach ($lines as &$val) {
79 $val = '<li>' . $this->cObj->stdWrap($val, $conf['innerStdWrap.']) . '</li>';
80 }
81 unset($val);
82 // Set header type:
83 $type = (int)$this->cObj->data['layout'];
84 // Compile list:
85 $out = '
86 <ul class="csc-bulletlist csc-bulletlist-' . $type . '">' . implode('', $lines) . '
87 </ul>';
88 // Return value
89 return $out;
90 }
91 }
92
93 /**
94 * Rendering the "Table" type content element, called from TypoScript (tt_content.table.20)
95 *
96 * @param string $content Content input. Not used, ignore.
97 * @param array $conf TypoScript configuration
98 * @return string HTML output.
99 */
100 public function render_table($content, $conf) {
101 // Look for hook before running default code for function
102 if ($hookObj = $this->hookRequest('render_table')) {
103 return $hookObj->render_table($content, $conf);
104 } else {
105 // Init FlexForm configuration
106 $this->pi_initPIflexForm();
107 // Get bodytext field content
108 $field = isset($conf['field']) && trim($conf['field']) ? trim($conf['field']) : 'bodytext';
109 $content = trim($this->cObj->data[$field]);
110 if ($content === '') {
111 return '';
112 }
113 // get flexform values
114 $caption = trim(htmlspecialchars($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_caption')));
115 $useTfoot = trim($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_tfoot'));
116 $headerPos = $this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_headerpos');
117 $noStyles = $this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_nostyles');
118 $tableClass = $this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_tableclass');
119 $delimiter = trim($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'tableparsing_delimiter', 's_parsing'));
120 if ($delimiter) {
121 $delimiter = chr((int)$delimiter);
122 } else {
123 $delimiter = '|';
124 }
125 $quotedInput = trim($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'tableparsing_quote', 's_parsing'));
126 if ($quotedInput) {
127 $quotedInput = chr((int)$quotedInput);
128 } else {
129 $quotedInput = '';
130 }
131 // Generate id prefix for accessible header
132 $headerScope = $headerPos == 'top' ? 'col' : 'row';
133 $headerIdPrefix = $headerScope . $this->cObj->data['uid'] . '-';
134 // Split into single lines (will become table-rows):
135 $rows = GeneralUtility::trimExplode(LF, $content);
136 reset($rows);
137 // Find number of columns to render:
138 $cols = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(
139 $this->cObj->data['cols'] ? $this->cObj->data['cols'] : count(str_getcsv(current($rows), $delimiter, $quotedInput)),
140 0,
141 100
142 );
143 // Traverse rows (rendering the table here)
144 $rCount = count($rows);
145 foreach ($rows as $k => $v) {
146 $cells = str_getcsv($v, $delimiter, $quotedInput);
147 $newCells = array();
148 for ($a = 0; $a < $cols; $a++) {
149 if (trim($cells[$a]) === '') {
150 $cells[$a] = '&nbsp;';
151 }
152 $cellAttribs = $noStyles ? '' : ($a > 0 && $cols - 1 == $a ? ' class="td-last td-' . $a . '"' : ' class="td-' . $a . '"');
153 if ($headerPos == 'top' && !$k || $headerPos == 'left' && !$a) {
154 $scope = ' scope="' . $headerScope . '"';
155 $scope .= ' id="' . $headerIdPrefix . ($headerScope == 'col' ? $a : $k) . '"';
156 $newCells[$a] = '
157 <th' . $cellAttribs . $scope . '>' . $this->cObj->stdWrap($cells[$a], $conf['innerStdWrap.']) . '</th>';
158 } else {
159 if (empty($headerPos)) {
160 $accessibleHeader = '';
161 } else {
162 $accessibleHeader = ' headers="' . $headerIdPrefix . ($headerScope == 'col' ? $a : $k) . '"';
163 }
164 $newCells[$a] = '
165 <td' . $cellAttribs . $accessibleHeader . '>' . $this->cObj->stdWrap($cells[$a], $conf['innerStdWrap.']) . '</td>';
166 }
167 }
168 if (!$noStyles) {
169 $oddEven = $k % 2 ? 'tr-odd' : 'tr-even';
170 $rowAttribs = $k > 0 && $rCount - 1 == $k ? ' class="' . $oddEven . ' tr-last"' : ' class="' . $oddEven . ' tr-' . $k . '"';
171 }
172 $rows[$k] = '
173 <tr' . $rowAttribs . '>' . implode('', $newCells) . '
174 </tr>';
175 }
176 $addTbody = 0;
177 $tableContents = '';
178 if ($caption) {
179 $tableContents .= '
180 <caption>' . $caption . '</caption>';
181 }
182 if ($headerPos == 'top' && $rows[0]) {
183 $tableContents .= '<thead>' . $rows[0] . '
184 </thead>';
185 unset($rows[0]);
186 $addTbody = 1;
187 }
188 if ($useTfoot) {
189 $tableContents .= '
190 <tfoot>' . $rows[($rCount - 1)] . '</tfoot>';
191 unset($rows[$rCount - 1]);
192 $addTbody = 1;
193 }
194 $tmpTable = implode('', $rows);
195 if ($addTbody) {
196 $tmpTable = '<tbody>' . $tmpTable . '</tbody>';
197 }
198 $tableContents .= $tmpTable;
199 // Set header type:
200 $type = (int)$this->cObj->data['layout'];
201 // Table tag params.
202 $tableTagParams = $this->getTableAttributes($conf, $type);
203 if (!$noStyles) {
204 $tableTagParams['class'] = 'contenttable contenttable-' . $type . ($tableClass ? ' ' . $tableClass : '') . $tableTagParams['class'];
205 } elseif ($tableClass) {
206 $tableTagParams['class'] = $tableClass;
207 }
208 // Compile table output:
209 $out = '
210 <table ' . GeneralUtility::implodeAttributes($tableTagParams) . '>' . $tableContents . '
211 </table>';
212 // Return value
213 return $out;
214 }
215 }
216
217 /**
218 * Rendering the "Filelinks" type content element, called from TypoScript (tt_content.uploads.20)
219 *
220 * @param string $content Content input. Not used, ignore.
221 * @param array $conf TypoScript configuration
222 * @return string HTML output.
223 */
224 public function render_uploads($content, $conf) {
225 // Look for hook before running default code for function
226 if ($hookObj = $this->hookRequest('render_uploads')) {
227 return $hookObj->render_uploads($content, $conf);
228 } else {
229 // Loading language-labels
230 $this->pi_loadLL();
231 $out = '';
232 // Set layout type:
233 $type = (int)$this->cObj->data['layout'];
234 // See if the file path variable is set, this takes precedence
235 $filePathConf = $this->cObj->stdWrap($conf['filePath'], $conf['filePath.']);
236 if ($filePathConf) {
237 $fileList = $this->cObj->filelist($filePathConf);
238 list($path) = explode('|', $filePathConf);
239 } else {
240 // Get the list of files from the field
241 $field = trim($conf['field']) ?: 'media';
242 $fileList = $this->cObj->data[$field];
243 $path = 'uploads/media/';
244 if (
245 is_array($GLOBALS['TCA']['tt_content']['columns'][$field]) &&
246 !empty($GLOBALS['TCA']['tt_content']['columns'][$field]['config']['uploadfolder'])
247 ) {
248 // In TCA-Array folders are saved without trailing slash, so $path.$fileName won't work
249 $path = $GLOBALS['TCA']['tt_content']['columns'][$field]['config']['uploadfolder'] . '/';
250 }
251 }
252 $path = trim($path);
253 // Explode into an array:
254 $fileArray = GeneralUtility::trimExplode(',', $fileList, TRUE);
255 // If there were files to list...:
256 if (count($fileArray)) {
257 // Get the descriptions for the files (if any):
258 $descriptions = GeneralUtility::trimExplode(LF, $this->cObj->data['imagecaption']);
259 // Get the titles for the files (if any)
260 $titles = GeneralUtility::trimExplode(LF, $this->cObj->data['titleText']);
261 // Get the alternative text for icons/thumbnails
262 $altTexts = GeneralUtility::trimExplode(LF, $this->cObj->data['altText']);
263 // Add the target to linkProc when explicitly set
264 if ($this->cObj->data['target']) {
265 $conf['linkProc.']['target'] = $this->cObj->data['target'];
266 unset($conf['linkProc.']['target.']);
267 }
268 // Adding hardcoded TS to linkProc configuration:
269 $conf['linkProc.']['path.']['current'] = 1;
270 if ($conf['linkProc.']['combinedLink']) {
271 $conf['linkProc.']['icon'] = $type > 0 ? 1 : 0;
272 } else {
273 // Always render icon - is inserted by PHP if needed.
274 $conf['linkProc.']['icon'] = 1;
275 // Temporary, internal split-token!
276 $conf['linkProc.']['icon.']['wrap'] = ' | //**//';
277 // ALways link the icon
278 $conf['linkProc.']['icon_link'] = 1;
279 }
280 $conf['linkProc.']['icon_image_ext_list'] = $type == 2 || $type == 3 ? $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] : '';
281 // If the layout is type 2 or 3 we will render an image based icon if possible.
282 if ($conf['labelStdWrap.']) {
283 $conf['linkProc.']['labelStdWrap.'] = $conf['labelStdWrap.'];
284 }
285 if ($conf['useSpacesInLinkText'] || $conf['stripFileExtensionFromLinkText']) {
286 $conf['linkProc.']['removePrependedNumbers'] = 0;
287 }
288 // Traverse the files found:
289 $filesData = array();
290 foreach ($fileArray as $key => $fileName) {
291 $absPath = GeneralUtility::getFileAbsFileName(GeneralUtility::resolveBackPath($path . $fileName));
292 if (@is_file($absPath)) {
293 $fI = pathinfo($fileName);
294 $filesData[$key] = array();
295 $currentPath = $path;
296 if (GeneralUtility::isFirstPartOfStr($fileName, '../../')) {
297 $currentPath = '';
298 $fileName = substr($fileName, 6);
299 }
300 $filesData[$key]['filename'] = $fileName;
301 $filesData[$key]['path'] = $currentPath;
302 $filesData[$key]['filesize'] = filesize($absPath);
303 $filesData[$key]['fileextension'] = strtolower($fI['extension']);
304 $filesData[$key]['description'] = trim($descriptions[$key]);
305 $filesData[$key]['titletext'] = trim($titles[$key]);
306 $filesData[$key]['alttext'] = trim($altTexts[$key]);
307 $conf['linkProc.']['title'] = trim($titles[$key]);
308 if (isset($altTexts[$key]) && !empty($altTexts[$key])) {
309 $altText = trim($altTexts[$key]);
310 } else {
311 $altText = sprintf($this->pi_getLL('uploads.icon'), $fileName);
312 }
313 $conf['linkProc.']['altText'] = ($conf['linkProc.']['iconCObject.']['altText'] = $altText);
314 $this->cObj->setCurrentVal($currentPath);
315 $GLOBALS['TSFE']->register['ICON_REL_PATH'] = $currentPath . $fileName;
316 $GLOBALS['TSFE']->register['filename'] = $filesData[$key]['filename'];
317 $GLOBALS['TSFE']->register['path'] = $filesData[$key]['path'];
318 $GLOBALS['TSFE']->register['fileSize'] = $filesData[$key]['filesize'];
319 $GLOBALS['TSFE']->register['fileExtension'] = $filesData[$key]['fileextension'];
320 $GLOBALS['TSFE']->register['description'] = $filesData[$key]['description'];
321 $GLOBALS['TSFE']->register['titleText'] = $filesData[$key]['titletext'];
322 $GLOBALS['TSFE']->register['altText'] = $filesData[$key]['alttext'];
323
324 $filesData[$key]['linkedFilenameParts'] = $this->beautifyFileLink(
325 explode('//**//', $this->cObj->filelink($fileName, $conf['linkProc.'])),
326 $fileName,
327 $conf['useSpacesInLinkText'],
328 $conf['stripFileExtensionFromLinkText']
329 );
330 }
331 }
332 // optionSplit applied to conf to allow differnt settings per file
333 $splitConf = $GLOBALS['TSFE']->tmpl->splitConfArray($conf, count($filesData));
334 // Now, lets render the list!
335 $outputEntries = array();
336 foreach ($filesData as $key => $fileData) {
337 $GLOBALS['TSFE']->register['linkedIcon'] = $fileData['linkedFilenameParts'][0];
338 $GLOBALS['TSFE']->register['linkedLabel'] = $fileData['linkedFilenameParts'][1];
339 $GLOBALS['TSFE']->register['filename'] = $fileData['filename'];
340 $GLOBALS['TSFE']->register['path'] = $fileData['path'];
341 $GLOBALS['TSFE']->register['description'] = $fileData['description'];
342 $GLOBALS['TSFE']->register['fileSize'] = $fileData['filesize'];
343 $GLOBALS['TSFE']->register['fileExtension'] = $fileData['fileextension'];
344 $GLOBALS['TSFE']->register['titleText'] = $fileData['titletext'];
345 $GLOBALS['TSFE']->register['altText'] = $fileData['alttext'];
346 $outputEntries[] = $this->cObj->cObjGetSingle($splitConf[$key]['itemRendering'], $splitConf[$key]['itemRendering.']);
347 }
348 if (isset($conf['outerWrap'])) {
349 // Wrap around the whole content
350 $outerWrap = $this->cObj->stdWrap($conf['outerWrap'], $conf['outerWrap.']);
351 } else {
352 // Table tag params
353 $tableTagParams = $this->getTableAttributes($conf, $type);
354 $tableTagParams['class'] = 'csc-uploads csc-uploads-' . $type;
355 $outerWrap = '<table ' . GeneralUtility::implodeAttributes($tableTagParams) . '>|</table>';
356 }
357 // Compile it all into table tags:
358 $out = $this->cObj->wrap(implode('', $outputEntries), $outerWrap);
359 }
360 // Return value
361 return $out;
362 }
363 }
364
365 /**
366 * Returns an array containing width relations for $colCount columns.
367 *
368 * Tries to use "colRelations" setting given by TS.
369 * uses "1:1" column relations by default.
370 *
371 * @param array $conf TS configuration for img
372 * @param int $colCount number of columns
373 * @return array
374 */
375 protected function getImgColumnRelations($conf, $colCount) {
376 $relations = array();
377 $equalRelations = array_fill(0, $colCount, 1);
378 $colRelationsTypoScript = trim($this->cObj->stdWrap($conf['colRelations'], $conf['colRelations.']));
379 if ($colRelationsTypoScript) {
380 // Try to use column width relations given by TS
381 $relationParts = explode(':', $colRelationsTypoScript);
382 // Enough columns defined?
383 if (count($relationParts) >= $colCount) {
384 $out = array();
385 for ($a = 0; $a < $colCount; $a++) {
386 $currentRelationValue = (int)$relationParts[$a];
387 if ($currentRelationValue >= 1) {
388 $out[$a] = $currentRelationValue;
389 } else {
390 GeneralUtility::devLog('colRelations used with a value smaller than 1 therefore colRelations setting is ignored.', $this->extKey, 2);
391 unset($out);
392 break;
393 }
394 }
395 if (max($out) / min($out) <= 10) {
396 $relations = $out;
397 } else {
398 GeneralUtility::devLog(
399 'The difference in size between the largest and smallest colRelation was not within' .
400 ' a factor of ten therefore colRelations setting is ignored..',
401 $this->extKey,
402 2
403 );
404 }
405 }
406 }
407 return $relations ?: $equalRelations;
408 }
409
410 /**
411 * Returns an array containing the image widths for an image row with $colCount columns.
412 *
413 * @param array $conf TS configuration of img
414 * @param int $colCount number of columns
415 * @param int $netW max usable width for images (without spaces and borders)
416 * @return array
417 */
418 protected function getImgColumnWidths($conf, $colCount, $netW) {
419 $columnWidths = array();
420 $colRelations = $this->getImgColumnRelations($conf, $colCount);
421 $accumWidth = 0;
422 $accumDesiredWidth = 0;
423 $relUnitCount = array_sum($colRelations);
424 for ($a = 0; $a < $colCount; $a++) {
425 // This much width is available for the remaining images in this row (int)
426 $availableWidth = $netW - $accumWidth;
427 // Theoretical width of resized image. (float)
428 $desiredWidth = $netW / $relUnitCount * $colRelations[$a];
429 // Add this width. $accumDesiredWidth becomes the desired horizontal position
430 $accumDesiredWidth += $desiredWidth;
431 // Calculate width by comparing actual and desired horizontal position.
432 // this evenly distributes rounding errors across all images in this row.
433 $suggestedWidth = round($accumDesiredWidth - $accumWidth);
434 // finalImgWidth may not exceed $availableWidth
435 $finalImgWidth = (int)min($availableWidth, $suggestedWidth);
436 $accumWidth += $finalImgWidth;
437 $columnWidths[$a] = $finalImgWidth;
438 }
439 return $columnWidths;
440 }
441
442 /**
443 * Rendering the IMGTEXT content element, called from TypoScript (tt_content.textpic.20)
444 *
445 * @param string $content Content input. Not used, ignore.
446 * @param array $conf TypoScript configuration. See TSRef "IMGTEXT". This function aims to be compatible.
447 * @return string HTML output.
448 * @coauthor Ernesto Baschny <ernst@cron-it.de>
449 * @coauthor Patrick Broens <patrick@patrickbroens.nl>
450 */
451 public function render_textpic($content, $conf) {
452 // Look for hook before running default code for function
453 if (method_exists($this, 'hookRequest') && ($hookObj = $this->hookRequest('render_textpic'))) {
454 return $hookObj->render_textpic($content, $conf);
455 }
456 $renderMethod = $this->cObj->stdWrap($conf['renderMethod'], $conf['renderMethod.']);
457 // Render using the default IMGTEXT code (table-based)
458 if (!$renderMethod || $renderMethod == 'table') {
459 return $this->cObj->IMGTEXT($conf);
460 }
461 $restoreRegisters = FALSE;
462 if (isset($conf['preRenderRegisters.'])) {
463 $restoreRegisters = TRUE;
464 $this->cObj->LOAD_REGISTER($conf['preRenderRegisters.'], 'LOAD_REGISTER');
465 }
466 // Specific configuration for the chosen rendering method
467 if (is_array($conf['rendering.'][$renderMethod . '.'])) {
468 $conf = array_replace_recursive($conf, $conf['rendering.'][$renderMethod . '.']);
469 }
470 // Image or Text with Image?
471 if (is_array($conf['text.'])) {
472 $content = $this->cObj->stdWrap($this->cObj->cObjGet($conf['text.'], 'text.'), $conf['text.']);
473 }
474 $imgList = trim($this->cObj->stdWrap($conf['imgList'], $conf['imgList.']));
475 if (!$imgList) {
476 // No images, that's easy
477 if ($restoreRegisters) {
478 $this->cObj->LOAD_REGISTER(array(), 'RESTORE_REGISTER');
479 }
480 return $content;
481 }
482 $imgs = GeneralUtility::trimExplode(',', $imgList, TRUE);
483 if (count($imgs) === 0) {
484 // The imgList was not empty but did only contain empty values
485 if ($restoreRegisters) {
486 $this->cObj->LOAD_REGISTER(array(), 'RESTORE_REGISTER');
487 }
488 return $content;
489 }
490 $imgStart = (int)$this->cObj->stdWrap($conf['imgStart'], $conf['imgStart.']);
491 $imgCount = count($imgs) - $imgStart;
492 $imgMax = (int)$this->cObj->stdWrap($conf['imgMax'], $conf['imgMax.']);
493 if ($imgMax) {
494 $imgCount = MathUtility::forceIntegerInRange($imgCount, 0, $imgMax);
495 }
496 $imgPath = $this->cObj->stdWrap($conf['imgPath'], $conf['imgPath.']);
497 // Does we need to render a "global caption" (below the whole image block)?
498 $renderGlobalCaption = !$conf['captionSplit'] && !$conf['imageTextSplit'] && is_array($conf['caption.']);
499 if ($imgCount == 1) {
500 // If we just have one image, the caption relates to the image, so it is not "global"
501 $renderGlobalCaption = FALSE;
502 }
503 $imgListContainsReferenceUids = (bool)(isset($conf['imgListContainsReferenceUids.'])
504 ? $this->cObj->stdWrap($conf['imgListContainsReferenceUids'], $conf['imgListContainsReferenceUids.'])
505 : $conf['imgListContainsReferenceUids']);
506 // Use the calculated information (amount of images, if global caption is wanted) to choose a different rendering method for the images-block
507 $GLOBALS['TSFE']->register['imageCount'] = $imgCount;
508 $GLOBALS['TSFE']->register['renderGlobalCaption'] = $renderGlobalCaption;
509 $fallbackRenderMethod = '';
510 if ($conf['fallbackRendering']) {
511 $fallbackRenderMethod = $this->cObj->cObjGetSingle($conf['fallbackRendering'], $conf['fallbackRendering.']);
512 }
513 if ($fallbackRenderMethod && is_array($conf['rendering.'][$fallbackRenderMethod . '.'])) {
514 $conf = array_replace_recursive($conf, $conf['rendering.'][$fallbackRenderMethod . '.']);
515 }
516 // Set the accessibility mode which uses a different type of markup, used 4.7+
517 $accessibilityMode = FALSE;
518 if (strpos(strtolower($renderMethod), 'caption') || strpos(strtolower($fallbackRenderMethod), 'caption')) {
519 $accessibilityMode = TRUE;
520 }
521 // Global caption
522 $globalCaption = '';
523 if ($renderGlobalCaption) {
524 $globalCaption = $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
525 }
526 // Positioning
527 $position = $this->cObj->stdWrap($conf['textPos'], $conf['textPos.']);
528 // 0,1,2 = center,right,left
529 $imagePosition = $position & 7;
530 // 0,8,16,24 (above,below,intext,intext-wrap)
531 $contentPosition = $position & 24;
532 $textMargin = (int)$this->cObj->stdWrap($conf['textMargin'], $conf['textMargin.']);
533 if (!$conf['textMargin_outOfText'] && $contentPosition < 16) {
534 $textMargin = 0;
535 }
536 $colspacing = (int)$this->cObj->stdWrap($conf['colSpace'], $conf['colSpace.']);
537 $border = (int)$this->cObj->stdWrap($conf['border'], $conf['border.']) ? 1 : 0;
538 $borderThickness = (int)$this->cObj->stdWrap($conf['borderThick'], $conf['borderThick.']);
539 $borderThickness = $borderThickness ?: 1;
540 $borderSpace = $conf['borderSpace'] && $border ? (int)$conf['borderSpace'] : 0;
541 // Generate cols
542 $cols = (int)$this->cObj->stdWrap($conf['cols'], $conf['cols.']);
543 $colCount = $cols > 1 ? $cols : 1;
544 if ($colCount > $imgCount) {
545 $colCount = $imgCount;
546 }
547 $rowCount = ceil($imgCount / $colCount);
548 // Generate rows
549 $rows = (int)$this->cObj->stdWrap($conf['rows'], $conf['rows.']);
550 if ($rows > 1) {
551 $rowCount = $rows;
552 if ($rowCount > $imgCount) {
553 $rowCount = $imgCount;
554 }
555 $colCount = $rowCount > 1 ? ceil($imgCount / $rowCount) : $imgCount;
556 }
557 // Max Width
558 $maxW = (int)$this->cObj->stdWrap($conf['maxW'], $conf['maxW.']);
559 $maxWInText = (int)$this->cObj->stdWrap($conf['maxWInText'], $conf['maxWInText.']);
560 $fiftyPercentWidthInText = round($maxW / 100 * 50);
561 // in Text
562 if ($contentPosition >= 16) {
563 if (!$maxWInText) {
564 // If maxWInText is not set, it's calculated to the 50% of the max
565 $maxW = $fiftyPercentWidthInText;
566 } else {
567 $maxW = $maxWInText;
568 }
569 }
570 // max usuable width for images (without spacers and borders)
571 $netW = $maxW - $colspacing * ($colCount - 1) - $colCount * $border * ($borderThickness + $borderSpace) * 2;
572 // Specify the maximum width for each column
573 $columnWidths = $this->getImgColumnWidths($conf, $colCount, $netW);
574 $image_compression = (int)$this->cObj->stdWrap($conf['image_compression'], $conf['image_compression.']);
575 $image_effects = (int)$this->cObj->stdWrap($conf['image_effects'], $conf['image_effects.']);
576 $image_frames = (int)$this->cObj->stdWrap($conf['image_frames.']['key'], $conf['image_frames.']['key.']);
577 // EqualHeight
578 $equalHeight = (int)$this->cObj->stdWrap($conf['equalH'], $conf['equalH.']);
579 if ($equalHeight) {
580 $relations_cols = array();
581 // contains the individual width of all images after scaling to $equalHeight
582 $imgWidths = array();
583 for ($a = 0; $a < $imgCount; $a++) {
584 $imgKey = $a + $imgStart;
585
586 /** @var $file \TYPO3\CMS\Core\Resource\File */
587 if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
588 if ($imgListContainsReferenceUids) {
589 $file = $this->getResourceFactory()->getFileReferenceObject((int)$imgs[$imgKey])->getOriginalFile();
590 } else {
591 $file = $this->getResourceFactory()->getFileObject((int)$imgs[$imgKey]);
592 }
593
594 } else {
595 $file = $this->getResourceFactory()->getFileObjectFromCombinedIdentifier($imgPath . $imgs[$imgKey]);
596 }
597
598 // relationship between the original height and the wished height
599 $rel = $file->getProperty('height') / $equalHeight;
600 // if relations is zero, then the addition of this value is omitted as the image is not expected to display because of some error.
601 if ($rel) {
602 $imgWidths[$a] = $file->getProperty('width') / $rel;
603 // counts the total width of the row with the new height taken into consideration.
604 $relations_cols[(int)floor($a / $colCount)] += $imgWidths[$a];
605 }
606 }
607 }
608 // Fetches pictures
609 $splitArr = array();
610 $splitArr['imgObjNum'] = $conf['imgObjNum'];
611 $splitArr = $GLOBALS['TSFE']->tmpl->splitConfArray($splitArr, $imgCount);
612 // Contains the width of every image row
613 $imageRowsFinalWidths = array();
614 // Array index of $imgsTag will be the same as in $imgs, but $imgsTag only contains the images that are actually shown
615 $imgsTag = array();
616 $origImages = array();
617 $rowIdx = 0;
618 for ($a = 0; $a < $imgCount; $a++) {
619 $imgKey = $a + $imgStart;
620 // If the image cannot be interpreted as integer (therefore filename and no FAL id), add the image path
621 if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
622 $totalImagePath = (int)$imgs[$imgKey];
623 $this->initializeCurrentFileInContentObjectRenderer($totalImagePath, $imgListContainsReferenceUids);
624 } else {
625 $totalImagePath = $imgPath . $imgs[$imgKey];
626 }
627 // register IMG_NUM is kept for backwards compatibility
628 $GLOBALS['TSFE']->register['IMAGE_NUM'] = $imgKey;
629 $GLOBALS['TSFE']->register['IMAGE_NUM_CURRENT'] = $imgKey;
630 $GLOBALS['TSFE']->register['ORIG_FILENAME'] = $totalImagePath;
631 $this->cObj->data[$this->cObj->currentValKey] = $totalImagePath;
632 $imgObjNum = (int)$splitArr[$a]['imgObjNum'];
633 $imgConf = $conf[$imgObjNum . '.'];
634 if ($equalHeight) {
635 if ($a % $colCount == 0) {
636 // A new row starts
637 // Reset accumulated net width
638 $accumWidth = 0;
639 // Reset accumulated desired width
640 $accumDesiredWidth = 0;
641 $rowTotalMaxW = $relations_cols[$rowIdx];
642 if ($rowTotalMaxW > $netW && $netW > 0) {
643 $scale = $rowTotalMaxW / $netW;
644 } else {
645 $scale = 1;
646 }
647 $desiredHeight = $equalHeight / $scale;
648 $rowIdx++;
649 }
650 // This much width is available for the remaining images in this row (int)
651 $availableWidth = $netW - $accumWidth;
652 // Theoretical width of resized image. (float)
653 $desiredWidth = $imgWidths[$a] / $scale;
654 // Add this width. $accumDesiredWidth becomes the desired horizontal position
655 $accumDesiredWidth += $desiredWidth;
656 // Calculate width by comparing actual and desired horizontal position.
657 // this evenly distributes rounding errors across all images in this row.
658 $suggestedWidth = round($accumDesiredWidth - $accumWidth);
659 // finalImgWidth may not exceed $availableWidth
660 $finalImgWidth = (int)min($availableWidth, $suggestedWidth);
661 $accumWidth += $finalImgWidth;
662 $imgConf['file.']['width'] = $finalImgWidth;
663 $imgConf['file.']['height'] = round($desiredHeight);
664 // other stuff will be calculated accordingly:
665 unset($imgConf['file.']['maxW']);
666 unset($imgConf['file.']['maxH']);
667 unset($imgConf['file.']['minW']);
668 unset($imgConf['file.']['minH']);
669 unset($imgConf['file.']['width.']);
670 unset($imgConf['file.']['maxW.']);
671 unset($imgConf['file.']['maxH.']);
672 unset($imgConf['file.']['minW.']);
673 unset($imgConf['file.']['minH.']);
674 } else {
675 $imgConf['file.']['maxW'] = $columnWidths[$a % $colCount];
676 }
677 $titleInLink = $this->cObj->stdWrap($imgConf['titleInLink'], $imgConf['titleInLink.']);
678 $titleInLinkAndImg = $this->cObj->stdWrap($imgConf['titleInLinkAndImg'], $imgConf['titleInLinkAndImg.']);
679 $oldATagParms = $GLOBALS['TSFE']->ATagParams;
680 if ($titleInLink) {
681 // Title in A-tag instead of IMG-tag
682 $titleText = trim($this->cObj->stdWrap($imgConf['titleText'], $imgConf['titleText.']));
683 if ($titleText) {
684 // This will be used by the IMAGE call later:
685 $GLOBALS['TSFE']->ATagParams .= ' title="' . htmlspecialchars($titleText) . '"';
686 }
687 }
688
689 // hook to allow custom rendering of a single element
690 // This hook is needed to render alternative content which is not just a plain image,
691 // like showing other FAL content, like videos, things which need to be embedded as JS, ...
692 $customRendering = '';
693 if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'])
694 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'])) {
695 $hookParameters = array(
696 'file' => $totalImagePath,
697 'imageConfiguration' => $imgConf
698 );
699
700 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'] as $reference) {
701 $customRendering = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($reference, $hookParameters, $this);
702 // if there is a renderer found, don't run through the other renderers
703 if (!empty($customRendering)) {
704 break;
705 }
706 }
707 }
708
709 if (!empty($customRendering)) {
710 $imgsTag[$imgKey] = $customRendering;
711 } elseif ($imgConf || $imgConf['file']) {
712 if ($this->cObj->image_effects[$image_effects]) {
713 $imgConf['file.']['params'] .= ' ' . $this->cObj->image_effects[$image_effects];
714 }
715 if ($image_frames) {
716 if (is_array($conf['image_frames.'][$image_frames . '.'])) {
717 $imgConf['file.']['m.'] = $conf['image_frames.'][$image_frames . '.'];
718 }
719 }
720 if ($image_compression && $imgConf['file'] != 'GIFBUILDER') {
721 if ($image_compression == 1) {
722 $tempImport = $imgConf['file.']['import'];
723 $tempImport_dot = $imgConf['file.']['import.'];
724 $tempTreatIdAsReference = $imgConf['file.']['treatIdAsReference'];
725 unset($imgConf['file.']);
726 $imgConf['file.']['import'] = $tempImport;
727 $imgConf['file.']['import.'] = $tempImport_dot;
728 $imgConf['file.']['treatIdAsReference'] = $tempTreatIdAsReference;
729 } elseif (isset($this->cObj->image_compression[$image_compression])) {
730 $imgConf['file.']['params'] .= ' ' . $this->cObj->image_compression[$image_compression]['params'];
731 $imgConf['file.']['ext'] = $this->cObj->image_compression[$image_compression]['ext'];
732 unset($imgConf['file.']['ext.']);
733 }
734 }
735 if ($titleInLink && !$titleInLinkAndImg) {
736 // Check if the image will be linked
737 $link = $this->cObj->imageLinkWrap('', $this->cObj->getCurrentFile() ?: $totalImagePath, $imgConf['imageLinkWrap.']);
738 if ($link) {
739 // Title in A-tag only (set above: ATagParams), not in IMG-tag
740 unset($imgConf['titleText']);
741 unset($imgConf['titleText.']);
742 $imgConf['emptyTitleHandling'] = 'removeAttr';
743 }
744 }
745 $imgsTag[$imgKey] = $this->cObj->IMAGE($imgConf);
746 } else {
747 // currentValKey !!!
748 $imgsTag[$imgKey] = $this->cObj->IMAGE(array('file' => $totalImagePath));
749 }
750 // Restore our ATagParams
751 $GLOBALS['TSFE']->ATagParams = $oldATagParms;
752 // Store the original filepath
753 $origImages[$imgKey] = $GLOBALS['TSFE']->lastImageInfo;
754 if ($GLOBALS['TSFE']->lastImageInfo[0] == 0) {
755 $imageRowsFinalWidths[(int)floor($a / $colCount)] += $this->cObj->data['imagewidth'];
756 } else {
757 $imageRowsFinalWidths[(int)floor($a / $colCount)] += $GLOBALS['TSFE']->lastImageInfo[0];
758 }
759 }
760 // How much space will the image-block occupy?
761 $imageBlockWidth = max($imageRowsFinalWidths) + $colspacing * ($colCount - 1) + $colCount * $border * ($borderSpace + $borderThickness) * 2;
762 $GLOBALS['TSFE']->register['rowwidth'] = $imageBlockWidth;
763 $GLOBALS['TSFE']->register['rowWidthPlusTextMargin'] = $imageBlockWidth + $textMargin;
764 // noRows is in fact just one ROW, with the amount of columns specified, where the images are placed in.
765 // noCols is just one COLUMN, each images placed side by side on each row
766 $noRows = $this->cObj->stdWrap($conf['noRows'], $conf['noRows.']);
767 $noCols = $this->cObj->stdWrap($conf['noCols'], $conf['noCols.']);
768 // noRows overrides noCols. They cannot exist at the same time.
769 if ($noRows) {
770 $noCols = 0;
771 $rowCount = 1;
772 }
773 if ($noCols) {
774 $colCount = 1;
775 }
776 // Edit icons:
777 if (!is_array($conf['editIcons.'])) {
778 $conf['editIcons.'] = array();
779 }
780 $editIconsHTML = $conf['editIcons'] && $GLOBALS['TSFE']->beUserLogin ? $this->cObj->editIcons('', $conf['editIcons'], $conf['editIcons.']) : '';
781 // If noRows, we need multiple imagecolumn wraps
782 $imageWrapCols = 1;
783 if ($noRows) {
784 $imageWrapCols = $colCount;
785 }
786 // User wants to separate the rows, but only do that if we do have rows
787 $separateRows = $this->cObj->stdWrap($conf['separateRows'], $conf['separateRows.']);
788 if ($noRows) {
789 $separateRows = 0;
790 }
791 if ($rowCount == 1) {
792 $separateRows = 0;
793 }
794 if ($accessibilityMode) {
795 $imagesInColumns = round($imgCount / ($rowCount * $colCount), 0, PHP_ROUND_HALF_UP);
796 // Apply optionSplit to the list of classes that we want to add to each column
797 $addClassesCol = $conf['addClassesCol'];
798 if (isset($conf['addClassesCol.'])) {
799 $addClassesCol = $this->cObj->stdWrap($addClassesCol, $conf['addClassesCol.']);
800 }
801 $addClassesColConf = $GLOBALS['TSFE']->tmpl->splitConfArray(array('addClassesCol' => $addClassesCol), $colCount);
802 // Apply optionSplit to the list of classes that we want to add to each image
803 $addClassesImage = $conf['addClassesImage'];
804 if (isset($conf['addClassesImage.'])) {
805 $addClassesImage = $this->cObj->stdWrap($addClassesImage, $conf['addClassesImage.']);
806 }
807 $addClassesImageConf = $GLOBALS['TSFE']->tmpl->splitConfArray(array('addClassesImage' => $addClassesImage), $imagesInColumns);
808 $rows = array();
809 $currentImage = 0;
810 // Set the class for the caption (split or global)
811 $classCaptionAlign = array(
812 'center' => 'csc-textpic-caption-c',
813 'right' => 'csc-textpic-caption-r',
814 'left' => 'csc-textpic-caption-l'
815 );
816 $captionAlign = $this->cObj->stdWrap($conf['captionAlign'], $conf['captionAlign.']);
817 // Iterate over the rows
818 for ($rowCounter = 1; $rowCounter <= $rowCount; $rowCounter++) {
819 $rowColumns = array();
820 // Iterate over the columns
821 for ($columnCounter = 1; $columnCounter <= $colCount; $columnCounter++) {
822 $columnImages = array();
823 // Iterate over the amount of images allowed in a column
824 for ($imagesCounter = 1; $imagesCounter <= $imagesInColumns; $imagesCounter++) {
825 $image = NULL;
826 $splitCaption = NULL;
827 $imageMarkers = ($captionMarkers = array());
828 $single = '&nbsp;';
829 // Set the key of the current image
830 $imageKey = $currentImage + $imgStart;
831 // Register IMAGE_NUM_CURRENT for the caption
832 $GLOBALS['TSFE']->register['IMAGE_NUM_CURRENT'] = $imageKey;
833 $this->cObj->data[$this->cObj->currentValKey] = $origImages[$imageKey]['origFile'];
834 if (MathUtility::canBeInterpretedAsInteger($imgs[$imageKey])) {
835 $this->initializeCurrentFileInContentObjectRenderer((int)$imgs[$imageKey], $imgListContainsReferenceUids);
836 } elseif (!isset($imgs[$imageKey])) {
837 // If not all columns in the last row are filled $imageKey gets larger than
838 // the array. In that case we clear the current file.
839 $this->cObj->setCurrentFile(NULL);
840 }
841 // Get the image if not an empty cell
842 if (isset($imgsTag[$imageKey])) {
843 $image = $this->cObj->stdWrap($imgsTag[$imageKey], $conf['imgTagStdWrap.']);
844 // Add the edit icons
845 if ($editIconsHTML) {
846 $image .= $this->cObj->stdWrap($editIconsHTML, $conf['editIconsStdWrap.']);
847 }
848 // Wrap the single image
849 $single = $this->cObj->stdWrap($image, $conf['singleStdWrap.']);
850 // Get the caption
851 if (!$renderGlobalCaption) {
852 $imageMarkers['caption'] = $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
853 if ($captionAlign) {
854 $captionMarkers['classes'] = ' ' . $classCaptionAlign[$captionAlign];
855 }
856 $imageMarkers['caption'] = $this->cObj->substituteMarkerArray($imageMarkers['caption'], $captionMarkers, '###|###', 1, 1);
857 }
858 if ($addClassesImageConf[$imagesCounter - 1]['addClassesImage']) {
859 $imageMarkers['classes'] = ' ' . $addClassesImageConf[($imagesCounter - 1)]['addClassesImage'];
860 }
861 }
862 $columnImages[] = $this->cObj->substituteMarkerArray($single, $imageMarkers, '###|###', 1, 1);
863 $currentImage++;
864 }
865 $rowColumn = $this->cObj->stdWrap(implode(LF, $columnImages), $conf['columnStdWrap.']);
866 // Start filling the markers for columnStdWrap
867 $columnMarkers = array();
868 if ($addClassesColConf[$columnCounter - 1]['addClassesCol']) {
869 $columnMarkers['classes'] = ' ' . $addClassesColConf[($columnCounter - 1)]['addClassesCol'];
870 }
871 $rowColumns[] = $this->cObj->substituteMarkerArray($rowColumn, $columnMarkers, '###|###', 1, 1);
872 }
873 if ($noRows) {
874 $rowConfiguration = $conf['noRowsStdWrap.'];
875 } elseif ($rowCounter == $rowCount) {
876 $rowConfiguration = $conf['lastRowStdWrap.'];
877 } else {
878 $rowConfiguration = $conf['rowStdWrap.'];
879 }
880 $row = $this->cObj->stdWrap(implode(LF, $rowColumns), $rowConfiguration);
881 // Start filling the markers for columnStdWrap
882 $rowMarkers = array();
883 $rows[] = $this->cObj->substituteMarkerArray($row, $rowMarkers, '###|###', 1, 1);
884 }
885 $images = $this->cObj->stdWrap(implode(LF, $rows), $conf['allStdWrap.']);
886 // Start filling the markers for allStdWrap
887 $allMarkers = array();
888 $classes = array();
889 // Add the global caption to the allStdWrap marker array if set
890 if ($globalCaption) {
891 $allMarkers['caption'] = $globalCaption;
892 if ($captionAlign) {
893 $classes[] = $classCaptionAlign[$captionAlign];
894 }
895 }
896 // Set the margin for image + text, no wrap always to avoid multiple stylesheets
897 $noWrapMargin = (int)(($maxWInText ? $maxWInText : $fiftyPercentWidthInText) + (int)$this->cObj->stdWrap($conf['textMargin'], $conf['textMargin.']));
898 $this->addPageStyle('.csc-textpic-intext-right-nowrap .csc-textpic-text', 'margin-right: ' . $noWrapMargin . 'px;');
899 $this->addPageStyle('.csc-textpic-intext-left-nowrap .csc-textpic-text', 'margin-left: ' . $noWrapMargin . 'px;');
900 // Beside Text where the image block width is not equal to maxW
901 if ($contentPosition == 24 && $maxW != $imageBlockWidth) {
902 $noWrapMargin = $imageBlockWidth + $textMargin;
903 // Beside Text, Right
904 if ($imagePosition == 1) {
905 $this->addPageStyle('.csc-textpic-intext-right-nowrap-' . $noWrapMargin . ' .csc-textpic-text', 'margin-right: ' . $noWrapMargin . 'px;');
906 $classes[] = 'csc-textpic-intext-right-nowrap-' . $noWrapMargin;
907 } elseif ($imagePosition == 2) {
908 $this->addPageStyle('.csc-textpic-intext-left-nowrap-' . $noWrapMargin . ' .csc-textpic-text', 'margin-left: ' . $noWrapMargin . 'px;');
909 $classes[] = 'csc-textpic-intext-left-nowrap-' . $noWrapMargin;
910 }
911 }
912 // Add the border class if needed
913 if ($border) {
914 $classes[] = $conf['borderClass'] ?: 'csc-textpic-border';
915 }
916 // Add the class for equal height if needed
917 if ($equalHeight) {
918 $classes[] = 'csc-textpic-equalheight';
919 }
920 $addClasses = $this->cObj->stdWrap($conf['addClasses'], $conf['addClasses.']);
921 if ($addClasses) {
922 $classes[] = $addClasses;
923 }
924 if ($classes) {
925 $class = ' ' . implode(' ', $classes);
926 }
927 // Fill the markers for the allStdWrap
928 $images = $this->cObj->substituteMarkerArray($images, $allMarkers, '###|###', 1, 1);
929 } else {
930 // Apply optionSplit to the list of classes that we want to add to each image
931 $addClassesImage = $conf['addClassesImage'];
932 if (isset($conf['addClassesImage.'])) {
933 $addClassesImage = $this->cObj->stdWrap($addClassesImage, $conf['addClassesImage.']);
934 }
935 $addClassesImageConf = $GLOBALS['TSFE']->tmpl->splitConfArray(array('addClassesImage' => $addClassesImage), $colCount);
936 // Render the images
937 $images = '';
938 for ($c = 0; $c < $imageWrapCols; $c++) {
939 $tmpColspacing = $colspacing;
940 if ($c == $imageWrapCols - 1 && $imagePosition == 2 || $c == 0 && ($imagePosition == 1 || $imagePosition == 0)) {
941 // Do not add spacing after column if we are first column (left) or last column (center/right)
942 $tmpColspacing = 0;
943 }
944 $thisImages = '';
945 $allRows = '';
946 $maxImageSpace = 0;
947 $imgsTagCount = count($imgsTag);
948 for ($i = $c; $i < $imgsTagCount; $i = $i + $imageWrapCols) {
949 $imgKey = $i + $imgStart;
950 $colPos = $i % $colCount;
951 if ($separateRows && $colPos == 0) {
952 $thisRow = '';
953 }
954 // Render one image
955 if ($origImages[$imgKey][0] == 0) {
956 $imageSpace = $this->cObj->data['imagewidth'] + $border * ($borderSpace + $borderThickness) * 2;
957 } else {
958 $imageSpace = $origImages[$imgKey][0] + $border * ($borderSpace + $borderThickness) * 2;
959 }
960 $GLOBALS['TSFE']->register['IMAGE_NUM'] = $imgKey;
961 $GLOBALS['TSFE']->register['IMAGE_NUM_CURRENT'] = $imgKey;
962 $GLOBALS['TSFE']->register['ORIG_FILENAME'] = $origImages[$imgKey]['origFile'];
963 $GLOBALS['TSFE']->register['imagewidth'] = $origImages[$imgKey][0];
964 $GLOBALS['TSFE']->register['imagespace'] = $imageSpace;
965 $GLOBALS['TSFE']->register['imageheight'] = $origImages[$imgKey][1];
966 if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
967 $this->initializeCurrentFileInContentObjectRenderer(intval($imgs[$imgKey]), $imgListContainsReferenceUids);
968 }
969 if ($imageSpace > $maxImageSpace) {
970 $maxImageSpace = $imageSpace;
971 }
972 $thisImage = '';
973 $thisImage .= $this->cObj->stdWrap($imgsTag[$imgKey], $conf['imgTagStdWrap.']);
974 if (!$renderGlobalCaption) {
975 $thisImage .= $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
976 }
977 if ($editIconsHTML) {
978 $thisImage .= $this->cObj->stdWrap($editIconsHTML, $conf['editIconsStdWrap.']);
979 }
980 $thisImage = $this->cObj->stdWrap($thisImage, $conf['oneImageStdWrap.']);
981 $classes = '';
982 if ($addClassesImageConf[$colPos]['addClassesImage']) {
983 $classes = ' ' . $addClassesImageConf[$colPos]['addClassesImage'];
984 }
985 $thisImage = str_replace('###CLASSES###', $classes, $thisImage);
986 if ($separateRows) {
987 $thisRow .= $thisImage;
988 } else {
989 $allRows .= $thisImage;
990 }
991 $GLOBALS['TSFE']->register['columnwidth'] = $maxImageSpace + $tmpColspacing;
992 // Close this row at the end (colCount), or the last row at the final end
993 if ($separateRows && $i + 1 == count($imgsTag)) {
994 // Close the very last row with either normal configuration or lastRow stdWrap
995 $allRows .= $this->cObj->stdWrap(
996 $thisRow,
997 is_array($conf['imageLastRowStdWrap.']) ? $conf['imageLastRowStdWrap.'] : $conf['imageRowStdWrap.']
998 );
999 } elseif ($separateRows && $colPos == $colCount - 1) {
1000 $allRows .= $this->cObj->stdWrap($thisRow, $conf['imageRowStdWrap.']);
1001 }
1002 }
1003 if ($separateRows) {
1004 $thisImages .= $allRows;
1005 } else {
1006 $thisImages .= $this->cObj->stdWrap($allRows, $conf['noRowsStdWrap.']);
1007 }
1008 if ($noRows) {
1009 // Only needed to make columns, rather than rows:
1010 $images .= $this->cObj->stdWrap($thisImages, $conf['imageColumnStdWrap.']);
1011 } else {
1012 $images .= $thisImages;
1013 }
1014 }
1015 // Add the global caption, if not split
1016 if ($globalCaption) {
1017 $images .= $globalCaption;
1018 }
1019 // CSS-classes
1020 $captionClass = '';
1021 $classCaptionAlign = array(
1022 'center' => 'csc-textpic-caption-c',
1023 'right' => 'csc-textpic-caption-r',
1024 'left' => 'csc-textpic-caption-l'
1025 );
1026 $captionAlign = $this->cObj->stdWrap($conf['captionAlign'], $conf['captionAlign.']);
1027 if ($captionAlign) {
1028 $captionClass = $classCaptionAlign[$captionAlign];
1029 }
1030 $borderClass = '';
1031 if ($border) {
1032 $borderClass = $conf['borderClass'] ?: 'csc-textpic-border';
1033 }
1034 // Multiple classes with all properties, to be styled in CSS
1035 $class = '';
1036 $class .= $borderClass ? ' ' . $borderClass : '';
1037 $class .= $captionClass ? ' ' . $captionClass : '';
1038 $class .= $equalHeight ? ' csc-textpic-equalheight' : '';
1039 $addClasses = $this->cObj->stdWrap($conf['addClasses'], $conf['addClasses.']);
1040 $class .= $addClasses ? ' ' . $addClasses : '';
1041 // Do we need a width in our wrap around images?
1042 $imgWrapWidth = '';
1043 if ($position == 0 || $position == 8) {
1044 // For 'center' we always need a width: without one, the margin:auto trick won't work
1045 $imgWrapWidth = $imageBlockWidth;
1046 }
1047 if ($rowCount > 1) {
1048 // For multiple rows we also need a width, so that the images will wrap
1049 $imgWrapWidth = $imageBlockWidth;
1050 }
1051 if ($globalCaption) {
1052 // If we have a global caption, we need the width so that the caption will wrap
1053 $imgWrapWidth = $imageBlockWidth;
1054 }
1055 // Wrap around the whole image block
1056 $GLOBALS['TSFE']->register['totalwidth'] = $imgWrapWidth;
1057 if ($imgWrapWidth) {
1058 $images = $this->cObj->stdWrap($images, $conf['imageStdWrap.']);
1059 } else {
1060 $images = $this->cObj->stdWrap($images, $conf['imageStdWrapNoWidth.']);
1061 }
1062 }
1063
1064 $output = str_replace(
1065 array(
1066 '###TEXT###',
1067 '###IMAGES###',
1068 '###CLASSES###'
1069 ),
1070 array(
1071 $content,
1072 $images,
1073 $class
1074 ),
1075 $this->cObj->cObjGetSingle($conf['layout'], $conf['layout.'])
1076 );
1077
1078 if ($restoreRegisters) {
1079 $this->cObj->LOAD_REGISTER(array(), 'RESTORE_REGISTER');
1080 }
1081
1082 return $output;
1083 }
1084
1085 /**
1086 * Loads the file / file reference object and sets it in the
1087 * currentFile property of the ContentObjectRenderer.
1088 *
1089 * This makes the file data available during image rendering.
1090 *
1091 * @param int $fileUid The UID of the file or file reference (depending on $treatAsReference) that should be loaded.
1092 * @param bool $treatAsReference If TRUE the given UID will be used to load a file reference otherwise it will be used to load a regular file.
1093 * @return void
1094 */
1095 protected function initializeCurrentFileInContentObjectRenderer($fileUid, $treatAsReference) {
1096 $resourceFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
1097 if ($treatAsReference) {
1098 $imageFile = $resourceFactory->getFileReferenceObject($fileUid);
1099 } else {
1100 $imageFile = $resourceFactory->getFileObject($fileUid);
1101 }
1102 $this->cObj->setCurrentFile($imageFile);
1103 }
1104
1105 /***********************************
1106 * Rendering of Content Element properties
1107 ***********************************/
1108
1109 /**
1110 * Add top or bottom margin to the content element
1111 *
1112 * Constructs and adds a class to the content element. This class selector
1113 * and its declaration are added to the specific page styles.
1114 *
1115 * @param string $content Content input. Not used, ignore.
1116 * @param array $configuration TypoScript configuration
1117 * @return string The class name
1118 */
1119 public function renderSpace($content, array $configuration) {
1120 // Look for hook before running default code for function
1121 if (method_exists($this, 'hookRequest') && ($hookObject = $this->hookRequest('renderSpace'))) {
1122 return $hookObject->renderSpace($content, $configuration);
1123 }
1124 if (isset($configuration['space']) && in_array($configuration['space'], array('before', 'after'))) {
1125 $constant = (int)$configuration['constant'];
1126 if ($configuration['space'] === 'before') {
1127 $value = $constant + $this->cObj->data['spaceBefore'];
1128 $declaration = 'margin-top: ' . $value . 'px !important;';
1129 } else {
1130 $value = $constant + $this->cObj->data['spaceAfter'];
1131 $declaration = 'margin-bottom: ' . $value . 'px !important;';
1132 }
1133 if (!empty($value)) {
1134 if ($configuration['classStdWrap.']) {
1135 $className = $this->cObj->stdWrap($value, $configuration['classStdWrap.']);
1136 } else {
1137 $className = $value;
1138 }
1139 $selector = '.' . trim($className);
1140 $this->addPageStyle($selector, $declaration);
1141 return $className;
1142 }
1143 }
1144 }
1145
1146 /************************************
1147 * Helper functions
1148 ************************************/
1149
1150 /**
1151 * Returns a link text string which replaces underscores in filename with
1152 * blanks.
1153 *
1154 * Has the possibility to cut off FileType.
1155 *
1156 * @param array $links
1157 * @param string $fileName
1158 * @param bool $useSpaces
1159 * @param bool $cutFileExtension
1160 * @return array modified array with new link text
1161 */
1162 protected function beautifyFileLink(array $links, $fileName, $useSpaces = FALSE, $cutFileExtension = FALSE) {
1163 $linkText = $fileName;
1164 if ($useSpaces) {
1165 $linkText = str_replace('_', ' ', $linkText);
1166 }
1167 if ($cutFileExtension) {
1168 $pos = strrpos($linkText, '.');
1169 $linkText = substr($linkText, 0, $pos);
1170 }
1171 $links[1] = str_replace('>' . $fileName . '<', '>' . htmlspecialchars($linkText) . '<', $links[1]);
1172 return $links;
1173 }
1174
1175 /**
1176 * Returns table attributes for uploads / tables.
1177 *
1178 * @param array $conf TypoScript configuration array
1179 * @param int $type The "layout" type
1180 * @return array Array with attributes inside.
1181 */
1182 public function getTableAttributes($conf, $type) {
1183 // Initializing:
1184 $tableTagParams_conf = $conf['tableParams_' . $type . '.'];
1185 $border = $this->cObj->data['table_border'] ? (int)$this->cObj->data['table_border'] : $tableTagParams_conf['border'];
1186 $cellSpacing = $this->cObj->data['table_cellspacing'] ? (int)$this->cObj->data['table_cellspacing'] : $tableTagParams_conf['cellspacing'];
1187 $cellPadding = $this->cObj->data['table_cellpadding'] ? (int)$this->cObj->data['table_cellpadding'] : $tableTagParams_conf['cellpadding'];
1188 $summary = trim(htmlspecialchars($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_summary')));
1189 // Create table attributes and classes array:
1190 $tableTagParams = ($classes = array());
1191 // Table attributes for all doctypes except HTML5
1192 if ($GLOBALS['TSFE']->config['config']['doctype'] !== 'html5') {
1193 $tableTagParams['border'] = $border;
1194 $tableTagParams['cellspacing'] = $cellSpacing;
1195 $tableTagParams['cellpadding'] = $cellPadding;
1196 if ($summary) {
1197 $tableTagParams['summary'] = $summary;
1198 }
1199 } else {
1200 if ($border) {
1201 // Border property has changed, now with class
1202 $borderClass = 'contenttable-border-' . $border;
1203 $borderDeclaration = 'border-width: ' . $border . 'px; border-style: solid;';
1204 $this->addPageStyle('.' . $borderClass, $borderDeclaration);
1205 $classes[] = $borderClass;
1206 }
1207 if ($cellSpacing) {
1208 // Border attribute for HTML5 is 1 when there is cell spacing
1209 $tableTagParams['border'] = 1;
1210 // Use CSS3 border-spacing in class to have cell spacing
1211 $cellSpacingClass = 'contenttable-cellspacing-' . $cellSpacing;
1212 $cellSpacingDeclaration = 'border-spacing: ' . $cellSpacing . 'px;';
1213 $this->addPageStyle('.' . $cellSpacingClass, $cellSpacingDeclaration);
1214 $classes[] = $cellSpacingClass;
1215 }
1216 if ($cellPadding) {
1217 // Cell padding property has changed, now with class
1218 $cellPaddingClass = 'contenttable-cellpadding-' . $cellPadding;
1219 $cellSpacingSelector = '.' . $cellPaddingClass . ' td, .' . $cellPaddingClass . ' th';
1220 $cellPaddingDeclaration = 'padding: ' . $cellPadding . 'px;';
1221 $this->addPageStyle($cellSpacingSelector, $cellPaddingDeclaration);
1222 $classes[] = $cellPaddingClass;
1223 }
1224 }
1225 // Background color is class
1226 if (isset($conf['color.'][$this->cObj->data['table_bgColor']]) && !empty($conf['color.'][$this->cObj->data['table_bgColor']])) {
1227 $classes[] = 'contenttable-color-' . $this->cObj->data['table_bgColor'];
1228 }
1229 if (!empty($classes)) {
1230 $tableTagParams['class'] = ' ' . implode(' ', $classes);
1231 }
1232 // Return result:
1233 return $tableTagParams;
1234 }
1235
1236 /**
1237 * Add a style to the page, specific for this page
1238 *
1239 * The selector can be a contextual selector, like '#id .class p'
1240 * The presence of the selector is checked to avoid multiple entries of the
1241 * same selector.
1242 *
1243 * @param string $selector The selector
1244 * @param string $declaration The declaration
1245 * @return void
1246 */
1247 protected function addPageStyle($selector, $declaration) {
1248 if (!isset($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'])) {
1249 $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'] = array();
1250 }
1251 if (!isset($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'][$selector])) {
1252 $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'][$selector] = TAB . $selector . ' { ' . $declaration . ' }';
1253 }
1254 }
1255
1256 /**
1257 * Returns an object reference to the hook object if any
1258 *
1259 * @param string $functionName Name of the function you want to call / hook key
1260 * @return object Hook object, if any. Otherwise NULL.
1261 */
1262 public function hookRequest($functionName) {
1263 // Hook: menuConfig_preProcessModMenu
1264 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks'][$functionName]) {
1265 $hookObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks'][$functionName]);
1266 if (method_exists($hookObj, $functionName)) {
1267 $hookObj->pObj = $this;
1268 return $hookObj;
1269 }
1270 }
1271 }
1272
1273 /**
1274 * Get the ResourceFactory
1275 *
1276 * @return \TYPO3\CMS\Core\Resource\ResourceFactory
1277 */
1278 protected function getResourceFactory() {
1279 return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
1280 }
1281
1282 }