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