TYPO3 CMS  TYPO3_8-7
CssStyledContentController.php
Go to the documentation of this file.
1 <?php
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 
20 
26 {
32  public $prefixId = 'tx_cssstyledcontent_pi1';
33 
39  public $scriptRelPath = 'Classes/Controller/CssStyledContentController.php';
40 
46  public $extKey = 'css_styled_content';
47 
51  public $conf = [];
52 
53  /***********************************
54  * Rendering of Content Elements:
55  ***********************************/
56 
64  public function render_table($content, $conf)
65  {
66  // Look for hook before running default code for function
67  if ($hookObj = $this->hookRequest('render_table')) {
68  return $hookObj->render_table($content, $conf);
69  }
70  // Init FlexForm configuration
71  $this->pi_initPIflexForm();
72  // Get bodytext field content
73  $field = isset($conf['field']) && trim($conf['field']) ? trim($conf['field']) : 'bodytext';
74  $content = trim($this->cObj->data[$field]);
75  if ($content === '') {
76  return '';
77  }
78  // Get configuration
79  $caption = trim($this->cObj->data['table_caption']);
80  $useTfoot = trim($this->cObj->data['table_tfoot']);
81  $headerPosition = trim($this->cObj->data['table_header_position']);
82  switch ($headerPosition) {
83  case '1':
84  $headerPos = 'top';
85  break;
86  case '2':
87  $headerPos = 'left';
88  break;
89  default:
90  $headerPos = '';
91  break;
92  }
93  $tableClass = trim($this->cObj->data['table_class']);
94  $delimiter = trim($this->cObj->data['table_delimiter']);
95  if ($delimiter) {
96  $delimiter = chr((int)$delimiter);
97  } else {
98  $delimiter = '|';
99  }
100  $quotedInput = trim($this->cObj->data['table_enclosure']);
101  if ($quotedInput) {
102  $quotedInput = chr((int)$quotedInput);
103  } else {
104  $quotedInput = '';
105  }
106  // Generate id prefix for accessible header
107  $headerScope = $headerPos === 'top' ? 'col' : 'row';
108  $headerIdPrefix = $headerScope . $this->cObj->data['uid'] . '-';
109  // Split into single lines (will become table-rows):
110  $rows = GeneralUtility::trimExplode(LF, $content);
111  reset($rows);
112  // Find number of columns to render:
114  $this->cObj->data['cols'] ? $this->cObj->data['cols'] : count(str_getcsv(current($rows), $delimiter, $quotedInput)),
115  0,
116  100
117  );
118  // Traverse rows (rendering the table here)
119  $rCount = count($rows);
120  foreach ($rows as $k => $v) {
121  $cells = str_getcsv($v, $delimiter, $quotedInput);
122  $newCells = [];
123  for ($a = 0; $a < $cols; $a++) {
124  if (trim($cells[$a]) === '') {
125  $cells[$a] = ' ';
126  }
127  $cells[$a] = preg_replace('|<br */?>|i', LF, $cells[$a]);
128  if ($headerPos === 'top' && !$k || $headerPos === 'left' && !$a) {
129  $scope = ' scope="' . $headerScope . '"';
130  $scope .= ' id="' . $headerIdPrefix . ($headerScope === 'col' ? $a : $k) . '"';
131  $newCells[$a] = '<th' . $scope . '>' . $this->cObj->stdWrap($cells[$a], $conf['innerStdWrap.']) . '</th>';
132  } else {
133  if (empty($headerPos)) {
134  $accessibleHeader = '';
135  } else {
136  $accessibleHeader = ' headers="' . $headerIdPrefix . ($headerScope === 'col' ? $a : $k) . '"';
137  }
138  $newCells[$a] = '<td' . $accessibleHeader . '>' . $this->cObj->stdWrap($cells[$a], $conf['innerStdWrap.']) . '</td>';
139  }
140  }
141  $rows[$k] = '<tr>' . implode('', $newCells) . '</tr>';
142  }
143  $addTbody = 0;
144  $tableContents = '';
145  if ($caption) {
146  $tableContents .= '
147  <caption>' . $caption . '</caption>';
148  }
149  if ($headerPos === 'top' && $rows[0]) {
150  $tableContents .= '<thead>' . $rows[0] . '</thead>';
151  unset($rows[0]);
152  $addTbody = 1;
153  }
154  if ($useTfoot) {
155  $tableContents .= '<tfoot>' . $rows[$rCount - 1] . '</tfoot>';
156  unset($rows[$rCount - 1]);
157  $addTbody = 1;
158  }
159  $tmpTable = implode('', $rows);
160  if ($addTbody) {
161  $tmpTable = '<tbody>' . $tmpTable . '</tbody>';
162  }
163  $tableContents .= $tmpTable;
164  // Set header type:
165  $type = (int)$this->cObj->data['layout'];
166  // Table tag params.
167  $tableTagParams = [];
168  $tableTagParams['class'] = 'contenttable contenttable-' . $type . ($tableClass ? ' contenttable-' . $tableClass : '');
169  // Compile table output:
170  $out = '<table ' . GeneralUtility::implodeAttributes($tableTagParams) . '>' . $tableContents . '</table>';
171  // Return value
172  return $out;
173  }
174 
185  protected function getImgColumnRelations($conf, $colCount)
186  {
187  $relations = [];
188  $equalRelations = array_fill(0, $colCount, 1);
189  $colRelationsTypoScript = trim($this->cObj->stdWrap($conf['colRelations'], $conf['colRelations.']));
190  if ($colRelationsTypoScript) {
191  // Try to use column width relations given by TS
192  $relationParts = explode(':', $colRelationsTypoScript);
193  // Enough columns defined?
194  if (count($relationParts) >= $colCount) {
195  $out = [];
196  for ($a = 0; $a < $colCount; $a++) {
197  $currentRelationValue = (int)$relationParts[$a];
198  if ($currentRelationValue >= 1) {
199  $out[$a] = $currentRelationValue;
200  } else {
201  GeneralUtility::devLog('colRelations used with a value smaller than 1 therefore colRelations setting is ignored.', $this->extKey, 2);
202  unset($out);
203  break;
204  }
205  }
206  if (max($out) / min($out) <= 10) {
207  $relations = $out;
208  } else {
210  'The difference in size between the largest and smallest colRelation was not within' .
211  ' a factor of ten therefore colRelations setting is ignored..',
212  $this->extKey,
213  2
214  );
215  }
216  }
217  }
218  return $relations ?: $equalRelations;
219  }
220 
229  protected function getImgColumnWidths($conf, $colCount, $netW)
230  {
231  $columnWidths = [];
232  $colRelations = $this->getImgColumnRelations($conf, $colCount);
233  $accumWidth = 0;
234  $accumDesiredWidth = 0;
235  $relUnitCount = array_sum($colRelations);
236  for ($a = 0; $a < $colCount; $a++) {
237  // This much width is available for the remaining images in this row (int)
238  $availableWidth = $netW - $accumWidth;
239  // Theoretical width of resized image. (float)
240  $desiredWidth = $netW / $relUnitCount * $colRelations[$a];
241  // Add this width. $accumDesiredWidth becomes the desired horizontal position
242  $accumDesiredWidth += $desiredWidth;
243  // Calculate width by comparing actual and desired horizontal position.
244  // this evenly distributes rounding errors across all images in this row.
245  $suggestedWidth = round($accumDesiredWidth - $accumWidth);
246  // finalImgWidth may not exceed $availableWidth
247  $finalImgWidth = (int)min($availableWidth, $suggestedWidth);
248  $accumWidth += $finalImgWidth;
249  $columnWidths[$a] = $finalImgWidth;
250  }
251  return $columnWidths;
252  }
253 
261  public function render_textpic($content, $conf)
262  {
263  // Look for hook before running default code for function
264  if (method_exists($this, 'hookRequest') && ($hookObj = $this->hookRequest('render_textpic'))) {
265  return $hookObj->render_textpic($content, $conf);
266  }
267  $renderMethod = $this->cObj->stdWrap($conf['renderMethod'], $conf['renderMethod.']);
268  // Render using the default IMGTEXT code (table-based)
269  if (!$renderMethod || $renderMethod === 'table') {
270  return $this->cObj->cObjGetSingle('IMGTEXT', $conf);
271  }
272 
273  $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
274 
275  $restoreRegisters = false;
276  if (isset($conf['preRenderRegisters.'])) {
277  $restoreRegisters = true;
278  $this->cObj->cObjGetSingle('LOAD_REGISTER', $conf['preRenderRegisters.']);
279  }
280  // Specific configuration for the chosen rendering method
281  if (is_array($conf['rendering.'][$renderMethod . '.'])) {
282  $conf = array_replace_recursive($conf, $conf['rendering.'][$renderMethod . '.']);
283  }
284  // Image or Text with Image?
285  if (is_array($conf['text.'])) {
286  $content = $this->cObj->stdWrap($this->cObj->cObjGet($conf['text.'], 'text.'), $conf['text.']);
287  }
288  $imgList = trim($this->cObj->stdWrap($conf['imgList'], $conf['imgList.']));
289  if (!$imgList) {
290  // No images, that's easy
291  if ($restoreRegisters) {
292  $this->cObj->cObjGetSingle('RESTORE_REGISTER', []);
293  }
294  return $content;
295  }
296  $imgs = GeneralUtility::trimExplode(',', $imgList, true);
297  if (empty($imgs)) {
298  // The imgList was not empty but did only contain empty values
299  if ($restoreRegisters) {
300  $this->cObj->cObjGetSingle('RESTORE_REGISTER', []);
301  }
302  return $content;
303  }
304  $imgStart = (int)$this->cObj->stdWrap($conf['imgStart'], $conf['imgStart.']);
305  $imgCount = count($imgs) - $imgStart;
306  $imgMax = (int)$this->cObj->stdWrap($conf['imgMax'], $conf['imgMax.']);
307  if ($imgMax) {
308  $imgCount = MathUtility::forceIntegerInRange($imgCount, 0, $imgMax);
309  }
310  $imgPath = $this->cObj->stdWrap($conf['imgPath'], $conf['imgPath.']);
311  // Does we need to render a "global caption" (below the whole image block)?
312  $renderGlobalCaption = !$conf['captionSplit'] && !$conf['imageTextSplit'] && is_array($conf['caption.']);
313  if ($imgCount == 1) {
314  // If we just have one image, the caption relates to the image, so it is not "global"
315  $renderGlobalCaption = false;
316  }
317  $imgListContainsReferenceUids = (bool)(isset($conf['imgListContainsReferenceUids.'])
318  ? $this->cObj->stdWrap($conf['imgListContainsReferenceUids'], $conf['imgListContainsReferenceUids.'])
319  : $conf['imgListContainsReferenceUids']);
320  // Use the calculated information (amount of images, if global caption is wanted) to choose a different rendering method for the images-block
321  $this->frontendController->register['imageCount'] = $imgCount;
322  $this->frontendController->register['renderGlobalCaption'] = $renderGlobalCaption;
323  $fallbackRenderMethod = '';
324  if ($conf['fallbackRendering']) {
325  $fallbackRenderMethod = $this->cObj->cObjGetSingle($conf['fallbackRendering'], $conf['fallbackRendering.']);
326  }
327  if ($fallbackRenderMethod && is_array($conf['rendering.'][$fallbackRenderMethod . '.'])) {
328  $conf = array_replace_recursive($conf, $conf['rendering.'][$fallbackRenderMethod . '.']);
329  }
330  // Set the accessibility mode which uses a different type of markup, used 4.7+
331  $accessibilityMode = false;
332  if (strpos(strtolower($renderMethod), 'caption') || strpos(strtolower($fallbackRenderMethod), 'caption')) {
333  $accessibilityMode = true;
334  }
335  // Global caption
336  $globalCaption = '';
337  if ($renderGlobalCaption) {
338  $globalCaption = $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
339  }
340  // Positioning
341  $position = $this->cObj->stdWrap($conf['textPos'], $conf['textPos.']);
342  // 0,1,2 = center,right,left
343  $imagePosition = $position & 7;
344  // 0,8,16,24 (above,below,intext,intext-wrap)
345  $contentPosition = $position & 24;
346  $textMargin = (int)$this->cObj->stdWrap($conf['textMargin'], $conf['textMargin.']);
347  if (!$conf['textMargin_outOfText'] && $contentPosition < 16) {
348  $textMargin = 0;
349  }
350  $colspacing = (int)$this->cObj->stdWrap($conf['colSpace'], $conf['colSpace.']);
351  $border = (int)$this->cObj->stdWrap($conf['border'], $conf['border.']) ? 1 : 0;
352  $borderThickness = (int)$this->cObj->stdWrap($conf['borderThick'], $conf['borderThick.']);
353  $borderThickness = $borderThickness ?: 1;
354  $borderSpace = $conf['borderSpace'] && $border ? (int)$conf['borderSpace'] : 0;
355  // Generate cols
356  $cols = (int)$this->cObj->stdWrap($conf['cols'], $conf['cols.']);
357  $colCount = $cols > 1 ? $cols : 1;
358  if ($colCount > $imgCount) {
359  $colCount = $imgCount;
360  }
361  $rowCount = ceil($imgCount / $colCount);
362  // Generate rows
363  $rows = (int)$this->cObj->stdWrap($conf['rows'], $conf['rows.']);
364  if ($rows > 1) {
365  $rowCount = $rows;
366  if ($rowCount > $imgCount) {
367  $rowCount = $imgCount;
368  }
369  $colCount = $rowCount > 1 ? ceil($imgCount / $rowCount) : $imgCount;
370  }
371  // Max Width
372  $maxW = (int)$this->cObj->stdWrap($conf['maxW'], $conf['maxW.']);
373  $maxWInText = (int)$this->cObj->stdWrap($conf['maxWInText'], $conf['maxWInText.']);
374  $fiftyPercentWidthInText = round($maxW / 100 * 50);
375  // in Text
376  if ($contentPosition >= 16) {
377  if (!$maxWInText) {
378  // If maxWInText is not set, it's calculated to the 50% of the max
379  $maxW = $fiftyPercentWidthInText;
380  } else {
381  $maxW = $maxWInText;
382  }
383  }
384  // max usuable width for images (without spacers and borders)
385  $netW = $maxW - $colspacing * ($colCount - 1) - $colCount * $border * ($borderThickness + $borderSpace) * 2;
386  // Specify the maximum width for each column
387  $columnWidths = $this->getImgColumnWidths($conf, $colCount, $netW);
388  $image_frames = (int)$this->cObj->stdWrap($conf['image_frames.']['key'], $conf['image_frames.']['key.']);
389  // EqualHeight
390  $equalHeight = (int)$this->cObj->stdWrap($conf['equalH'], $conf['equalH.']);
391  if ($equalHeight) {
392  $relations_cols = [];
393  // contains the individual width of all images after scaling to $equalHeight
394  $imgWidths = [];
395  for ($a = 0; $a < $imgCount; $a++) {
396  $imgKey = $a + $imgStart;
397 
399  if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
400  if ($imgListContainsReferenceUids) {
401  $file = $this->getResourceFactory()->getFileReferenceObject((int)$imgs[$imgKey])->getOriginalFile();
402  } else {
403  $file = $this->getResourceFactory()->getFileObject((int)$imgs[$imgKey]);
404  }
405  } else {
406  $file = $this->getResourceFactory()->getFileObjectFromCombinedIdentifier($imgPath . $imgs[$imgKey]);
407  }
408 
409  // relationship between the original height and the wished height
410  $rel = $file->getProperty('height') / $equalHeight;
411  // if relations is zero, then the addition of this value is omitted as the image is not expected to display because of some error.
412  if ($rel) {
413  $imgWidths[$a] = $file->getProperty('width') / $rel;
414  // counts the total width of the row with the new height taken into consideration.
415  $relations_cols[(int)floor($a / $colCount)] += $imgWidths[$a];
416  }
417  }
418  }
419  // Fetches pictures
420  $splitArr = [];
421  $splitArr['imgObjNum'] = $conf['imgObjNum'];
422  $splitArr = $typoScriptService->explodeConfigurationForOptionSplit($splitArr, (int)$imgCount);
423  // Contains the width of every image row
424  $imageRowsFinalWidths = [];
425  // Array index of $imgsTag will be the same as in $imgs, but $imgsTag only contains the images that are actually shown
426  $imgsTag = [];
427  $origImages = [];
428  $rowIdx = 0;
429  for ($a = 0; $a < $imgCount; $a++) {
430  $imgKey = $a + $imgStart;
431  // If the image cannot be interpreted as integer (therefore filename and no FAL id), add the image path
432  if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
433  $totalImagePath = (int)$imgs[$imgKey];
434  $this->initializeCurrentFileInContentObjectRenderer($totalImagePath, $imgListContainsReferenceUids);
435  } else {
436  $totalImagePath = $imgPath . $imgs[$imgKey];
437  }
438  // register IMG_NUM is kept for backwards compatibility
439  $this->frontendController->register['IMAGE_NUM'] = $imgKey;
440  $this->frontendController->register['IMAGE_NUM_CURRENT'] = $imgKey;
441  $this->frontendController->register['ORIG_FILENAME'] = $totalImagePath;
442  $this->cObj->data[$this->cObj->currentValKey] = $totalImagePath;
443  $imgObjNum = (int)$splitArr[$a]['imgObjNum'];
444  $imgConf = $conf[$imgObjNum . '.'];
445  if ($equalHeight) {
446  if ($a % $colCount == 0) {
447  // A new row starts
448  // Reset accumulated net width
449  $accumWidth = 0;
450  // Reset accumulated desired width
451  $accumDesiredWidth = 0;
452  $rowTotalMaxW = $relations_cols[$rowIdx];
453  if ($rowTotalMaxW > $netW && $netW > 0) {
454  $scale = $rowTotalMaxW / $netW;
455  } else {
456  $scale = 1;
457  }
458  $desiredHeight = $equalHeight / $scale;
459  $rowIdx++;
460  }
461  // This much width is available for the remaining images in this row (int)
462  $availableWidth = $netW - $accumWidth;
463  // Theoretical width of resized image. (float)
464  $desiredWidth = $imgWidths[$a] / $scale;
465  // Add this width. $accumDesiredWidth becomes the desired horizontal position
466  $accumDesiredWidth += $desiredWidth;
467  // Calculate width by comparing actual and desired horizontal position.
468  // this evenly distributes rounding errors across all images in this row.
469  $suggestedWidth = round($accumDesiredWidth - $accumWidth);
470  // finalImgWidth may not exceed $availableWidth
471  $finalImgWidth = (int)min($availableWidth, $suggestedWidth);
472  $accumWidth += $finalImgWidth;
473  $imgConf['file.']['width'] = $finalImgWidth;
474  $imgConf['file.']['height'] = round($desiredHeight);
475  // other stuff will be calculated accordingly:
476  unset($imgConf['file.']['maxW']);
477  unset($imgConf['file.']['maxH']);
478  unset($imgConf['file.']['minW']);
479  unset($imgConf['file.']['minH']);
480  unset($imgConf['file.']['width.']);
481  unset($imgConf['file.']['maxW.']);
482  unset($imgConf['file.']['maxH.']);
483  unset($imgConf['file.']['minW.']);
484  unset($imgConf['file.']['minH.']);
485  } else {
486  $imgConf['file.']['maxW'] = $columnWidths[$a % $colCount];
487  }
488  $titleInLink = $this->cObj->stdWrap($imgConf['titleInLink'], $imgConf['titleInLink.']);
489  $titleInLinkAndImg = $this->cObj->stdWrap($imgConf['titleInLinkAndImg'], $imgConf['titleInLinkAndImg.']);
490  $oldATagParms = $this->frontendController->ATagParams;
491  if ($titleInLink) {
492  // Title in A-tag instead of IMG-tag
493  $titleText = trim($this->cObj->stdWrap($imgConf['titleText'], $imgConf['titleText.']));
494  if ($titleText) {
495  // This will be used by the IMAGE call later:
496  $this->frontendController->ATagParams .= ' title="' . htmlspecialchars($titleText) . '"';
497  }
498  }
499 
500  // hook to allow custom rendering of a single element
501  // This hook is needed to render alternative content which is not just a plain image,
502  // like showing other FAL content, like videos, things which need to be embedded as JS, ...
503  $customRendering = '';
504  if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'])
505  && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'])) {
506  $hookParameters = [
507  'file' => $totalImagePath,
508  'imageConfiguration' => $imgConf
509  ];
510 
511  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks']['render_singleMediaElement'] as $reference) {
512  $customRendering = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($reference, $hookParameters, $this);
513  // if there is a renderer found, don't run through the other renderers
514  if (!empty($customRendering)) {
515  break;
516  }
517  }
518  }
519 
520  if (!empty($customRendering)) {
521  $imgsTag[$imgKey] = $customRendering;
522  } elseif ($imgConf || $imgConf['file']) {
523  if ($image_frames) {
524  if (is_array($conf['image_frames.'][$image_frames . '.'])) {
525  $imgConf['file.']['m.'] = $conf['image_frames.'][$image_frames . '.'];
526  }
527  }
528  if ($titleInLink && !$titleInLinkAndImg) {
529  // Check if the image will be linked
530  $link = $this->cObj->imageLinkWrap('', $this->cObj->getCurrentFile() ?: $totalImagePath, $imgConf['imageLinkWrap.']);
531  if ($link) {
532  // Title in A-tag only (set above: ATagParams), not in IMG-tag
533  unset($imgConf['titleText']);
534  unset($imgConf['titleText.']);
535  $imgConf['emptyTitleHandling'] = 'removeAttr';
536  }
537  }
538  $imgsTag[$imgKey] = $this->cObj->cObjGetSingle('IMAGE', $imgConf);
539  } else {
540  // currentValKey !!!
541  $imgsTag[$imgKey] = $this->cObj->cObjGetSingle('IMAGE', ['file' => $totalImagePath]);
542  }
543  // Restore our ATagParams
544  $this->frontendController->ATagParams = $oldATagParms;
545  // Store the original filepath
546  $origImages[$imgKey] = $this->frontendController->lastImageInfo;
547  if ($this->frontendController->lastImageInfo[0] == 0) {
548  $imageRowsFinalWidths[(int)floor($a / $colCount)] += $this->cObj->data['imagewidth'];
549  } else {
550  $imageRowsFinalWidths[(int)floor($a / $colCount)] += $this->frontendController->lastImageInfo[0];
551  }
552  }
553  // How much space will the image-block occupy?
554  $imageBlockWidth = max($imageRowsFinalWidths) + $colspacing * ($colCount - 1) + $colCount * $border * ($borderSpace + $borderThickness) * 2;
555  $this->frontendController->register['rowwidth'] = $imageBlockWidth;
556  $this->frontendController->register['rowWidthPlusTextMargin'] = $imageBlockWidth + $textMargin;
557  // Edit icons:
558  if (!is_array($conf['editIcons.'])) {
559  $conf['editIcons.'] = [];
560  }
561  $editIconsHTML = $conf['editIcons'] && $this->frontendController->beUserLogin ? $this->cObj->editIcons('', $conf['editIcons'], $conf['editIcons.']) : '';
562  $imageWrapCols = 1;
563  // User wants to separate the rows, but only do that if we do have rows
564  $separateRows = $this->cObj->stdWrap($conf['separateRows'], $conf['separateRows.']);
565  if ($rowCount == 1) {
566  $separateRows = 0;
567  }
568  if ($accessibilityMode) {
569  $imagesInColumns = round($imgCount / ($rowCount * $colCount), 0, PHP_ROUND_HALF_UP);
570  // Apply optionSplit to the list of classes that we want to add to each column
571  $addClassesCol = $conf['addClassesCol'];
572  if (isset($conf['addClassesCol.'])) {
573  $addClassesCol = $this->cObj->stdWrap($addClassesCol, $conf['addClassesCol.']);
574  }
575  $addClassesColConf = $typoScriptService->explodeConfigurationForOptionSplit(['addClassesCol' => $addClassesCol], $colCount);
576  // Apply optionSplit to the list of classes that we want to add to each image
577  $addClassesImage = $conf['addClassesImage'];
578  if (isset($conf['addClassesImage.'])) {
579  $addClassesImage = $this->cObj->stdWrap($addClassesImage, $conf['addClassesImage.']);
580  }
581  $addClassesImageConf = $typoScriptService->explodeConfigurationForOptionSplit(['addClassesImage' => $addClassesImage], $imagesInColumns);
582  $rows = [];
583  $currentImage = 0;
584  // Iterate over the rows
585  for ($rowCounter = 1; $rowCounter <= $rowCount; $rowCounter++) {
586  $rowColumns = [];
587  // Iterate over the columns
588  for ($columnCounter = 1; $columnCounter <= $colCount; $columnCounter++) {
589  $columnImages = [];
590  // Iterate over the amount of images allowed in a column
591  for ($imagesCounter = 1; $imagesCounter <= $imagesInColumns; $imagesCounter++) {
592  $image = null;
593  $splitCaption = null;
594  $imageMarkers = ($captionMarkers = []);
595  $single = '&nbsp;';
596  // Set the key of the current image
597  $imageKey = $currentImage + $imgStart;
598  // Register IMAGE_NUM_CURRENT for the caption
599  $this->frontendController->register['IMAGE_NUM_CURRENT'] = $imageKey;
600  $this->cObj->data[$this->cObj->currentValKey] = $origImages[$imageKey]['origFile'];
601  if (MathUtility::canBeInterpretedAsInteger($imgs[$imageKey])) {
602  $this->initializeCurrentFileInContentObjectRenderer((int)$imgs[$imageKey], $imgListContainsReferenceUids);
603  } elseif (!isset($imgs[$imageKey])) {
604  // If not all columns in the last row are filled $imageKey gets larger than
605  // the array. In that case we clear the current file.
606  $this->cObj->setCurrentFile(null);
607  }
608  // Get the image if not an empty cell
609  if (isset($imgsTag[$imageKey])) {
610  $image = $this->cObj->stdWrap($imgsTag[$imageKey], $conf['imgTagStdWrap.']);
611  // Add the edit icons
612  if ($editIconsHTML) {
613  $image .= $this->cObj->stdWrap($editIconsHTML, $conf['editIconsStdWrap.']);
614  }
615  // Wrap the single image
616  $single = $this->cObj->stdWrap($image, $conf['singleStdWrap.']);
617  // Get the caption
618  if (!$renderGlobalCaption) {
619  $imageMarkers['caption'] = $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
620  $imageMarkers['caption'] = $this->templateService->substituteMarkerArray($imageMarkers['caption'], $captionMarkers, '###|###', 1, 1);
621  }
622  if ($addClassesImageConf[$imagesCounter - 1]['addClassesImage']) {
623  $imageMarkers['classes'] = ' ' . $addClassesImageConf[$imagesCounter - 1]['addClassesImage'];
624  }
625  }
626  $columnImages[] = $this->templateService->substituteMarkerArray($single, $imageMarkers, '###|###', 1, 1);
627  $currentImage++;
628  }
629  $rowColumn = $this->cObj->stdWrap(implode(LF, $columnImages), $conf['columnStdWrap.']);
630  // Start filling the markers for columnStdWrap
631  $columnMarkers = [];
632  if ($addClassesColConf[$columnCounter - 1]['addClassesCol']) {
633  $columnMarkers['classes'] = ' ' . $addClassesColConf[$columnCounter - 1]['addClassesCol'];
634  }
635  $rowColumns[] = $this->templateService->substituteMarkerArray($rowColumn, $columnMarkers, '###|###', 1, 1);
636  }
637  if ($rowCounter == $rowCount) {
638  $rowConfiguration = $conf['lastRowStdWrap.'];
639  } else {
640  $rowConfiguration = $conf['rowStdWrap.'];
641  }
642  $row = $this->cObj->stdWrap(implode(LF, $rowColumns), $rowConfiguration);
643  // Start filling the markers for columnStdWrap
644  $rowMarkers = [];
645  $rows[] = $this->templateService->substituteMarkerArray($row, $rowMarkers, '###|###', 1, 1);
646  }
647  $images = $this->cObj->stdWrap(implode(LF, $rows), $conf['allStdWrap.']);
648  // Start filling the markers for allStdWrap
649  $allMarkers = [];
650  $classes = [];
651  // Add the global caption to the allStdWrap marker array if set
652  if ($globalCaption) {
653  $allMarkers['caption'] = $globalCaption;
654  }
655  // Set the margin for image + text, no wrap always to avoid multiple stylesheets
656  $noWrapMargin = (int)(($maxWInText ? $maxWInText : $fiftyPercentWidthInText) + (int)$this->cObj->stdWrap($conf['textMargin'], $conf['textMargin.']));
657  $this->addPageStyle('.csc-textpic-intext-right-nowrap .csc-textpic-text', 'margin-right: ' . $noWrapMargin . 'px;');
658  $this->addPageStyle('.csc-textpic-intext-left-nowrap .csc-textpic-text', 'margin-left: ' . $noWrapMargin . 'px;');
659  // Beside Text where the image block width is not equal to maxW
660  if ($contentPosition == 24 && $maxW != $imageBlockWidth) {
661  $noWrapMargin = $imageBlockWidth + $textMargin;
662  // Beside Text, Right
663  if ($imagePosition == 1) {
664  $this->addPageStyle('.csc-textpic-intext-right-nowrap-' . $noWrapMargin . ' .csc-textpic-text', 'margin-right: ' . $noWrapMargin . 'px;');
665  $classes[] = 'csc-textpic-intext-right-nowrap-' . $noWrapMargin;
666  } elseif ($imagePosition == 2) {
667  $this->addPageStyle('.csc-textpic-intext-left-nowrap-' . $noWrapMargin . ' .csc-textpic-text', 'margin-left: ' . $noWrapMargin . 'px;');
668  $classes[] = 'csc-textpic-intext-left-nowrap-' . $noWrapMargin;
669  }
670  }
671  // Add the border class if needed
672  if ($border) {
673  $classes[] = $conf['borderClass'] ?: 'csc-textpic-border';
674  }
675  // Add the class for equal height if needed
676  if ($equalHeight) {
677  $classes[] = 'csc-textpic-equalheight';
678  }
679  $addClasses = $this->cObj->stdWrap($conf['addClasses'], $conf['addClasses.']);
680  if ($addClasses) {
681  $classes[] = $addClasses;
682  }
683  if ($classes) {
684  $class = ' ' . implode(' ', $classes);
685  }
686  // Fill the markers for the allStdWrap
687  $images = $this->templateService->substituteMarkerArray($images, $allMarkers, '###|###', 1, 1);
688  } else {
689  // Apply optionSplit to the list of classes that we want to add to each image
690  $addClassesImage = $conf['addClassesImage'];
691  if (isset($conf['addClassesImage.'])) {
692  $addClassesImage = $this->cObj->stdWrap($addClassesImage, $conf['addClassesImage.']);
693  }
694  $addClassesImageConf = $typoScriptService->explodeConfigurationForOptionSplit(['addClassesImage' => $addClassesImage], $colCount);
695  // Render the images
696  $images = '';
697  for ($c = 0; $c < $imageWrapCols; $c++) {
698  $tmpColspacing = $colspacing;
699  if ($c == $imageWrapCols - 1 && $imagePosition == 2 || $c == 0 && ($imagePosition == 1 || $imagePosition == 0)) {
700  // Do not add spacing after column if we are first column (left) or last column (center/right)
701  $tmpColspacing = 0;
702  }
703  $thisImages = '';
704  $allRows = '';
705  $maxImageSpace = 0;
706  $imgsTagCount = count($imgsTag);
707  for ($i = $c; $i < $imgsTagCount; $i = $i + $imageWrapCols) {
708  $imgKey = $i + $imgStart;
709  $colPos = $i % $colCount;
710  if ($separateRows && $colPos == 0) {
711  $thisRow = '';
712  }
713  // Render one image
714  if ($origImages[$imgKey][0] == 0) {
715  $imageSpace = $this->cObj->data['imagewidth'] + $border * ($borderSpace + $borderThickness) * 2;
716  } else {
717  $imageSpace = $origImages[$imgKey][0] + $border * ($borderSpace + $borderThickness) * 2;
718  }
719  $this->frontendController->register['IMAGE_NUM'] = $imgKey;
720  $this->frontendController->register['IMAGE_NUM_CURRENT'] = $imgKey;
721  $this->frontendController->register['ORIG_FILENAME'] = $origImages[$imgKey]['origFile'];
722  $this->frontendController->register['imagewidth'] = $origImages[$imgKey][0];
723  $this->frontendController->register['imagespace'] = $imageSpace;
724  $this->frontendController->register['imageheight'] = $origImages[$imgKey][1];
725  if (MathUtility::canBeInterpretedAsInteger($imgs[$imgKey])) {
726  $this->initializeCurrentFileInContentObjectRenderer(intval($imgs[$imgKey]), $imgListContainsReferenceUids);
727  }
728  if ($imageSpace > $maxImageSpace) {
729  $maxImageSpace = $imageSpace;
730  }
731  $thisImage = '';
732  $thisImage .= $this->cObj->stdWrap($imgsTag[$imgKey], $conf['imgTagStdWrap.']);
733  if (!$renderGlobalCaption) {
734  $thisImage .= $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']);
735  }
736  if ($editIconsHTML) {
737  $thisImage .= $this->cObj->stdWrap($editIconsHTML, $conf['editIconsStdWrap.']);
738  }
739  $thisImage = $this->cObj->stdWrap($thisImage, $conf['oneImageStdWrap.']);
740  $classes = '';
741  if ($addClassesImageConf[$colPos]['addClassesImage']) {
742  $classes = ' ' . $addClassesImageConf[$colPos]['addClassesImage'];
743  }
744  $thisImage = str_replace('###CLASSES###', $classes, $thisImage);
745  if ($separateRows) {
746  $thisRow .= $thisImage;
747  } else {
748  $allRows .= $thisImage;
749  }
750  $this->frontendController->register['columnwidth'] = $maxImageSpace + $tmpColspacing;
751  // Close this row at the end (colCount), or the last row at the final end
752  if ($separateRows && $i + 1 === count($imgsTag)) {
753  // Close the very last row with either normal configuration or lastRow stdWrap
754  $allRows .= $this->cObj->stdWrap(
755  $thisRow,
756  is_array($conf['imageLastRowStdWrap.']) ? $conf['imageLastRowStdWrap.'] : $conf['imageRowStdWrap.']
757  );
758  } elseif ($separateRows && $colPos == $colCount - 1) {
759  $allRows .= $this->cObj->stdWrap($thisRow, $conf['imageRowStdWrap.']);
760  }
761  }
762  $thisImages .= $allRows;
763  $images .= $thisImages;
764  }
765  // Add the global caption, if not split
766  if ($globalCaption) {
767  $images .= $globalCaption;
768  }
769  // CSS-classes
770  $borderClass = '';
771  if ($border) {
772  $borderClass = $conf['borderClass'] ?: 'csc-textpic-border';
773  }
774  // Multiple classes with all properties, to be styled in CSS
775  $class = '';
776  $class .= $borderClass ? ' ' . $borderClass : '';
777  $class .= $equalHeight ? ' csc-textpic-equalheight' : '';
778  $addClasses = $this->cObj->stdWrap($conf['addClasses'], $conf['addClasses.']);
779  $class .= $addClasses ? ' ' . $addClasses : '';
780  // Do we need a width in our wrap around images?
781  $imgWrapWidth = '';
782  if ($position == 0 || $position == 8) {
783  // For 'center' we always need a width: without one, the margin:auto trick won't work
784  $imgWrapWidth = $imageBlockWidth;
785  }
786  if ($rowCount > 1) {
787  // For multiple rows we also need a width, so that the images will wrap
788  $imgWrapWidth = $imageBlockWidth;
789  }
790  if ($globalCaption) {
791  // If we have a global caption, we need the width so that the caption will wrap
792  $imgWrapWidth = $imageBlockWidth;
793  }
794  // Wrap around the whole image block
795  $this->frontendController->register['totalwidth'] = $imgWrapWidth;
796  if ($imgWrapWidth) {
797  $images = $this->cObj->stdWrap($images, $conf['imageStdWrap.']);
798  } else {
799  $images = $this->cObj->stdWrap($images, $conf['imageStdWrapNoWidth.']);
800  }
801  }
802 
803  $output = str_replace(
804  [
805  '###TEXT###',
806  '###IMAGES###',
807  '###CLASSES###'
808  ],
809  [
810  $content,
811  $images,
812  $class
813  ],
814  $this->cObj->cObjGetSingle($conf['layout'], $conf['layout.'])
815  );
816 
817  if ($restoreRegisters) {
818  $this->cObj->cObjGetSingle('RESTORE_REGISTER', []);
819  }
820 
821  return $output;
822  }
823 
833  protected function initializeCurrentFileInContentObjectRenderer($fileUid, $treatAsReference)
834  {
836  if ($treatAsReference) {
837  $imageFile = $resourceFactory->getFileReferenceObject($fileUid);
838  } else {
839  $imageFile = $resourceFactory->getFileObject($fileUid);
840  }
841  $this->cObj->setCurrentFile($imageFile);
842  }
843 
844  /***********************************
845  * Rendering of Content Element properties
846  ***********************************/
847 
858  public function renderSpace($content, array $configuration)
859  {
860  // Look for hook before running default code for function
861  if (method_exists($this, 'hookRequest') && ($hookObject = $this->hookRequest('renderSpace'))) {
862  return $hookObject->renderSpace($content, $configuration);
863  }
864  if (isset($configuration['space']) && in_array($configuration['space'], ['before', 'after'])) {
865  $constant = (int)$configuration['constant'];
866  if ($configuration['space'] === 'before') {
867  $value = $constant + $this->cObj->data['spaceBefore'];
868  $declaration = 'margin-top: ' . $value . 'px !important;';
869  } else {
870  $value = $constant + $this->cObj->data['spaceAfter'];
871  $declaration = 'margin-bottom: ' . $value . 'px !important;';
872  }
873  if (!empty($value)) {
874  if ($configuration['classStdWrap.']) {
875  $className = $this->cObj->stdWrap($value, $configuration['classStdWrap.']);
876  } else {
877  $className = $value;
878  }
879  $selector = '.' . trim($className);
880  $this->addPageStyle($selector, $declaration);
881  return $className;
882  }
883  }
884  }
885 
886  /************************************
887  * Helper functions
888  ************************************/
889 
897  public function getTableAttributes($conf, $type)
898  {
899  // Initializing:
900  $tableTagParams_conf = $conf['tableParams_' . $type . '.'];
901  $border = $this->cObj->data['table_border'] ? (int)$this->cObj->data['table_border'] : $tableTagParams_conf['border'];
902  $cellSpacing = $this->cObj->data['table_cellspacing'] ? (int)$this->cObj->data['table_cellspacing'] : $tableTagParams_conf['cellspacing'];
903  $cellPadding = $this->cObj->data['table_cellpadding'] ? (int)$this->cObj->data['table_cellpadding'] : $tableTagParams_conf['cellpadding'];
904  $summary = trim(htmlspecialchars($this->pi_getFFvalue($this->cObj->data['pi_flexform'], 'acctables_summary')));
905  // Create table attributes and classes array:
906  $tableTagParams = ($classes = []);
907  // Table attributes for all doctypes except HTML5
908  if ($this->frontendController->config['config']['doctype'] !== 'html5') {
909  $tableTagParams['border'] = $border;
910  $tableTagParams['cellspacing'] = $cellSpacing;
911  $tableTagParams['cellpadding'] = $cellPadding;
912  if ($summary) {
913  $tableTagParams['summary'] = $summary;
914  }
915  } else {
916  if ($border) {
917  // Border property has changed, now with class
918  $borderClass = 'contenttable-border-' . $border;
919  $borderDeclaration = 'border-width: ' . $border . 'px; border-style: solid;';
920  $this->addPageStyle('.' . $borderClass, $borderDeclaration);
921  $classes[] = $borderClass;
922  }
923  if ($cellSpacing) {
924  // Border attribute for HTML5 is 1 when there is cell spacing
925  $tableTagParams['border'] = 1;
926  // Use CSS3 border-spacing in class to have cell spacing
927  $cellSpacingClass = 'contenttable-cellspacing-' . $cellSpacing;
928  $cellSpacingDeclaration = 'border-spacing: ' . $cellSpacing . 'px;';
929  $this->addPageStyle('.' . $cellSpacingClass, $cellSpacingDeclaration);
930  $classes[] = $cellSpacingClass;
931  }
932  if ($cellPadding) {
933  // Cell padding property has changed, now with class
934  $cellPaddingClass = 'contenttable-cellpadding-' . $cellPadding;
935  $cellSpacingSelector = '.' . $cellPaddingClass . ' td, .' . $cellPaddingClass . ' th';
936  $cellPaddingDeclaration = 'padding: ' . $cellPadding . 'px;';
937  $this->addPageStyle($cellSpacingSelector, $cellPaddingDeclaration);
938  $classes[] = $cellPaddingClass;
939  }
940  }
941  // Background color is class
942  if (isset($conf['color.'][$this->cObj->data['table_bgColor']]) && !empty($conf['color.'][$this->cObj->data['table_bgColor']])) {
943  $classes[] = 'contenttable-color-' . $this->cObj->data['table_bgColor'];
944  }
945  if (!empty($classes)) {
946  $tableTagParams['class'] = ' ' . implode(' ', $classes);
947  }
948  // Return result:
949  return $tableTagParams;
950  }
951 
962  protected function addPageStyle($selector, $declaration)
963  {
964  if (!isset($this->frontendController->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'])) {
965  $this->frontendController->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'] = [];
966  }
967  if (!isset($this->frontendController->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'][$selector])) {
968  $this->frontendController->tmpl->setup['plugin.']['tx_cssstyledcontent.']['_CSS_PAGE_STYLE'][$selector] = TAB . $selector . ' { ' . $declaration . ' }';
969  }
970  }
971 
978  public function hookRequest($functionName)
979  {
980  // Hook: menuConfig_preProcessModMenu
981  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks'][$functionName]) {
982  $hookObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['css_styled_content']['pi1_hooks'][$functionName]);
983  if (method_exists($hookObj, $functionName)) {
984  $hookObj->pObj = $this;
985  return $hookObj;
986  }
987  }
988  }
989 
995  protected function getResourceFactory()
996  {
997  return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
998  }
999 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
pi_getFFvalue($T3FlexForm_array, $fieldName, $sheet='sDEF', $lang='lDEF', $value='vDEF')
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']