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