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