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