[TASK] Use "bool" in @param notation
[Packages/TYPO3.CMS.git] / typo3 / sysext / cshmanual / Classes / Controller / HelpModuleController.php
1 <?php
2 namespace TYPO3\CMS\Cshmanual\Controller;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Script Class for rendering the Context Sensitive Help documents,
22 * either the single display in the small pop-up window or the full-table view in the larger window.
23 *
24 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
25 */
26 class HelpModuleController {
27
28 /**
29 * @var string
30 */
31 public $allowedHTML = '<strong><em><b><i>';
32
33 /**
34 * For these vars, see init()
35 * If set access to fields and tables is checked. Should be done for TRUE database tables.
36 *
37 * @var bool
38 */
39 public $limitAccess;
40
41 /**
42 * The "table" key
43 *
44 * @var string
45 */
46 public $table;
47
48 /**
49 * The "field" key
50 *
51 * @var string
52 */
53 public $field;
54
55 /**
56 * Key used to point to the right CSH resource
57 * In simple cases, is equal to $table
58 *
59 * @var string
60 */
61 protected $mainKey;
62
63 /**
64 * Internal, static: GPvar
65 * Table/Field id
66 *
67 * @var string
68 */
69 public $tfID;
70
71 /**
72 * Back (previous tfID)
73 *
74 * @var string
75 */
76 public $back;
77
78 /**
79 * If set, then in TOC mode the FULL manual will be printed as well!
80 *
81 * @var bool
82 */
83 public $renderALL;
84
85 /**
86 * Content accumulation
87 *
88 * @var string
89 */
90 public $content;
91
92 /**
93 * Glossary words
94 *
95 * @var array
96 */
97 public $glossaryWords;
98
99 /**
100 * URL to help module
101 *
102 * @var string
103 */
104 protected $moduleUrl;
105
106 /**
107 * Initialize the class for various input etc.
108 *
109 * @return void
110 */
111 public function init() {
112 $this->moduleUrl = BackendUtility::getModuleUrl('help_cshmanual');
113 // Setting GPvars:
114 $this->tfID = GeneralUtility::_GP('tfID');
115 // Sanitizes the tfID using whitelisting.
116 if (!preg_match('/^[a-zA-Z0-9_\\-\\.\\*]*$/', $this->tfID)) {
117 $this->tfID = '';
118 }
119 $this->back = GeneralUtility::_GP('back');
120 $this->renderALL = GeneralUtility::_GP('renderALL');
121 // Set internal table/field to the parts of "tfID" incoming var.
122 $identifierParts = explode('.', $this->tfID);
123 // The table is the first item
124 $this->table = array_shift($identifierParts);
125 $this->mainKey = $this->table;
126 // The field is the second one
127 $this->field = array_shift($identifierParts);
128 // There may be extra parts for FlexForms
129 if (count($identifierParts) > 0) {
130 // There's at least one extra part
131 $extraIdentifierInformation = array();
132 $extraIdentifierInformation[] = array_shift($identifierParts);
133 // If the ds_pointerField contains a comma, it means the choice of FlexForm DS
134 // is determined by 2 parameters. In this case we have an extra identifier part
135 if (strpos($GLOBALS['TCA'][$this->table]['columns'][$this->field]['config']['ds_pointerField'], ',') !== FALSE) {
136 $extraIdentifierInformation[] = array_shift($identifierParts);
137 }
138 // The remaining parts make up the FlexForm field name itself
139 // (reassembled with dots)
140 $flexFormField = implode('.', $identifierParts);
141 // Assemble a different main key and switch field to use FlexForm field name
142 $this->mainKey .= '.' . $this->field;
143 foreach ($extraIdentifierInformation as $extraKey) {
144 $this->mainKey .= '.' . $extraKey;
145 }
146 $this->field = $flexFormField;
147 }
148 // limitAccess is checked if the $this->table really IS a table (and if the user is NOT a translator who should see all!)
149 $showAllToUser = BackendUtility::isModuleSetInTBE_MODULES('txllxmltranslateM1') && $GLOBALS['BE_USER']->check('modules', 'txllxmltranslateM1');
150 $this->limitAccess = isset($GLOBALS['TCA'][$this->table]) ? !$showAllToUser : FALSE;
151 $GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_view_help.xlf', 1);
152 }
153
154 /**
155 * Main function, rendering the display
156 *
157 * @return void
158 */
159 public function main() {
160 if ($this->field == '*') {
161 // If ALL fields is supposed to be shown:
162 $this->createGlossaryIndex();
163 $this->content .= $this->render_Table($this->mainKey);
164 } elseif ($this->tfID) {
165 // ... otherwise show only single field:
166 $this->createGlossaryIndex();
167 $this->content .= $this->render_Single($this->mainKey, $this->field);
168 } else {
169 // Render Table Of Contents if nothing else:
170 $this->content .= $this->render_TOC();
171 }
172
173 $this->doc = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Template\\DocumentTemplate');
174 $this->doc->backPath = $GLOBALS['BACK_PATH'];
175 $this->doc->setModuleTemplate('EXT:cshmanual/Resources/Private/Templates/cshmanual.html');
176
177 $markers = array('CONTENT' => $this->content);
178
179 $this->content = $this->doc->moduleBody(array(), array(), $markers);
180 $this->content = $this->doc->render($GLOBALS['LANG']->getLL('title'), $this->content);
181 }
182
183 /**
184 * Outputting the accumulated content to screen
185 *
186 * @return void
187 */
188 public function printContent() {
189 echo $this->content;
190 }
191
192 /************************************
193 * Rendering main modes
194 ************************************/
195
196 /**
197 * Creates Table Of Contents and possibly "Full Manual" mode if selected.
198 *
199 * @return string HTML content
200 */
201 public function render_TOC() {
202 // Initialize:
203 $CSHkeys = array_flip(array_keys($GLOBALS['TCA_DESCR']));
204 $TCAkeys = array_keys($GLOBALS['TCA']);
205 $outputSections = array();
206 $tocArray = array();
207 // TYPO3 Core Features:
208 $GLOBALS['LANG']->loadSingleTableDescription('xMOD_csh_corebe');
209 $this->render_TOC_el('xMOD_csh_corebe', 'core', $outputSections, $tocArray, $CSHkeys);
210 // Backend Modules:
211 $loadModules = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Module\\ModuleLoader');
212 $loadModules->load($GLOBALS['TBE_MODULES']);
213 foreach ($loadModules->modules as $mainMod => $info) {
214 $cshKey = '_MOD_' . $mainMod;
215 if ($CSHkeys[$cshKey]) {
216 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
217 $this->render_TOC_el($cshKey, 'modules', $outputSections, $tocArray, $CSHkeys);
218 }
219 if (is_array($info['sub'])) {
220 foreach ($info['sub'] as $subMod => $subInfo) {
221 $cshKey = '_MOD_' . $mainMod . '_' . $subMod;
222 if ($CSHkeys[$cshKey]) {
223 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
224 $this->render_TOC_el($cshKey, 'modules', $outputSections, $tocArray, $CSHkeys);
225 }
226 }
227 }
228 }
229 // Database Tables:
230 foreach ($TCAkeys as $table) {
231 // Load descriptions for table $table
232 $GLOBALS['LANG']->loadSingleTableDescription($table);
233 if (is_array($GLOBALS['TCA_DESCR'][$table]['columns']) && $GLOBALS['BE_USER']->check('tables_select', $table)) {
234 $this->render_TOC_el($table, 'tables', $outputSections, $tocArray, $CSHkeys);
235 }
236 }
237 // Extensions
238 foreach ($CSHkeys as $cshKey => $value) {
239 if (GeneralUtility::isFirstPartOfStr($cshKey, 'xEXT_') && !isset($GLOBALS['TCA'][$cshKey])) {
240 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
241 $this->render_TOC_el($cshKey, 'extensions', $outputSections, $tocArray, $CSHkeys);
242 }
243 }
244 // Glossary
245 foreach ($CSHkeys as $cshKey => $value) {
246 if (GeneralUtility::isFirstPartOfStr($cshKey, 'xGLOSSARY_') && !isset($GLOBALS['TCA'][$cshKey])) {
247 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
248 $this->render_TOC_el($cshKey, 'glossary', $outputSections, $tocArray, $CSHkeys);
249 }
250 }
251 // Other:
252 foreach ($CSHkeys as $cshKey => $value) {
253 if (!GeneralUtility::isFirstPartOfStr($cshKey, '_MOD_') && !isset($GLOBALS['TCA'][$cshKey])) {
254 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
255 $this->render_TOC_el($cshKey, 'other', $outputSections, $tocArray, $CSHkeys);
256 }
257 }
258
259 // COMPILE output:
260 $output = '';
261 $output .= '<h1>' . $GLOBALS['LANG']->getLL('manual_title', TRUE) . '</h1>';
262 $output .= '<p class="lead">' . $GLOBALS['LANG']->getLL('description', TRUE) . '</p>';
263
264 $output .= '<h2>' . $GLOBALS['LANG']->getLL('TOC', TRUE) . '</h2>' . $this->render_TOC_makeTocList($tocArray);
265 if (!$this->renderALL) {
266 $output .= '
267 <br/>
268 <p class="c-nav"><a href="' . htmlspecialchars($this->moduleUrl) . '&amp;renderALL=1">' . $GLOBALS['LANG']->getLL('full_manual', TRUE) . '</a></p>';
269 }
270 if ($this->renderALL) {
271 $output .= '
272
273 <h2>' . $GLOBALS['LANG']->getLL('full_manual_chapters', TRUE) . '</h2>' . implode('
274
275
276 <!-- NEW SECTION: -->
277 ', $outputSections);
278 }
279 $output .= '<hr /><p class="manual-title">' . BackendUtility::TYPO3_copyRightNotice() . '</p>';
280 return $output;
281 }
282
283 /**
284 * Creates a TOC list element and renders corresponding HELP content if "renderALL" mode is set.
285 *
286 * @param string $table CSH key / Table name
287 * @param string $tocCat TOC category keyword: "core", "modules", "tables", "other
288 * @param array $outputSections Array for accumulation of rendered HELP Content (in "renderALL" mode). Passed by reference!
289 * @param array $tocArray TOC array; Here TOC index elements are created. Passed by reference!
290 * @param array $CSHkeys CSH keys array. Every item rendered will be unset in this array so finally we can see what CSH keys are not processed yet. Passed by reference!
291 * @return void
292 */
293 public function render_TOC_el($table, $tocCat, &$outputSections, &$tocArray, &$CSHkeys) {
294 // Render full manual right here!
295 if ($this->renderALL) {
296 $outputSections[$table] = $this->render_Table($table);
297 if ($outputSections[$table]) {
298 $outputSections[$table] = '
299
300 <!-- New CSHkey/Table: ' . $table . ' -->
301 <p class="c-nav"><a name="ANCHOR_' . $table . '" href="#">' . $GLOBALS['LANG']->getLL('to_top', TRUE) . '</a></p>
302 <h2>' . $this->getTableFieldLabel($table) . '</h2>
303
304 ' . $outputSections[$table];
305 $tocArray[$tocCat][$table] = '<a href="#ANCHOR_' . $table . '">' . $this->getTableFieldLabel($table) . '</a>';
306 } else {
307 unset($outputSections[$table]);
308 }
309 } else {
310 // Only TOC:
311 $tocArray[$tocCat][$table] = '<p><a href="' . htmlspecialchars($this->moduleUrl) . '&amp;tfID=' . rawurlencode(($table . '.*')) . '">' . $this->getTableFieldLabel($table) . '</a></p>';
312 }
313 // Unset CSH key:
314 unset($CSHkeys[$table]);
315 }
316
317 /**
318 * Renders the TOC index as a HTML bullet list from TOC array
319 *
320 * @param array $tocArray ToC Array.
321 * @return string HTML bullet list for index.
322 */
323 public function render_TOC_makeTocList($tocArray) {
324 // The Various manual sections:
325 $keys = explode(',', 'core,modules,tables,extensions,glossary,other');
326 // Create TOC bullet list:
327 $output = '';
328 foreach ($keys as $tocKey) {
329 if (is_array($tocArray[$tocKey])) {
330 $output .= '
331 <li>' . $GLOBALS['LANG']->getLL(('TOC_' . $tocKey), TRUE) . '
332 <ul>
333 <li>' . implode('</li>
334 <li>', $tocArray[$tocKey]) . '</li>
335 </ul>
336 </li>';
337 }
338 }
339 // Compile TOC:
340 $output = '
341
342 <!-- TOC: -->
343 <div class="c-toc">
344 <ul>
345 ' . $output . '
346 </ul>
347 </div>';
348 return $output;
349 }
350
351 /**
352 * Render CSH for a full cshKey/table
353 *
354 * @param string $key Full CSH key (may be different from table name)
355 * @param string $table CSH key / table name
356 * @return string HTML output
357 */
358 public function render_Table($key, $table = NULL) {
359 $output = '';
360 // Take default key if not explicitly specified
361 if ($table === NULL) {
362 $table = $key;
363 }
364 // Load descriptions for table $table
365 $GLOBALS['LANG']->loadSingleTableDescription($key);
366 if (is_array($GLOBALS['TCA_DESCR'][$key]['columns']) && (!$this->limitAccess || $GLOBALS['BE_USER']->check('tables_select', $table))) {
367 // Initialize variables:
368 $parts = array();
369 // Reserved for header of table
370 $parts[0] = '';
371 // Traverse table columns as listed in TCA_DESCR
372 foreach ($GLOBALS['TCA_DESCR'][$key]['columns'] as $field => $_) {
373 $fieldValue = isset($GLOBALS['TCA'][$key]) && (string)$field !== '' ? $GLOBALS['TCA'][$key]['columns'][$field] : array();
374 if (is_array($fieldValue) && (!$this->limitAccess || !$fieldValue['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $table . ':' . $field))) {
375 if (!$field) {
376 // Header
377 $parts[0] = $this->printItem($key, '', 1);
378 } else {
379 // Field
380 $parts[] = $this->printItem($key, $field, 1);
381 }
382 }
383 }
384 if (!$parts[0]) {
385 unset($parts[0]);
386 }
387 $output .= implode('<br />', $parts);
388 }
389 // Substitute glossary words:
390 $output = $this->substituteGlossaryWords($output);
391 // TOC link:
392 if (!$this->renderALL) {
393 $tocLink = '<p class="c-nav"><a href="' . htmlspecialchars($this->moduleUrl) . '">' . $GLOBALS['LANG']->getLL('goToToc', TRUE) . '</a></p>';
394 $output = $tocLink . '
395 <br/>' . $output . '
396 <br />' . $tocLink;
397 }
398 return $output;
399 }
400
401 /**
402 * Renders CSH for a single field.
403 *
404 * @param string $key CSH key / table name
405 * @param string $field Sub key / field name
406 * @return string HTML output
407 */
408 public function render_Single($key, $field) {
409 $output = '';
410 // Load the description field
411 $GLOBALS['LANG']->loadSingleTableDescription($key);
412 // Render single item
413 $output .= $this->printItem($key, $field);
414 // Substitute glossary words:
415 $output = $this->substituteGlossaryWords($output);
416 // Link to Full table description and TOC:
417 $getLLKey = $this->limitAccess ? 'fullDescription' : 'fullDescription_module';
418 $output .= '<br />
419 <p class="c-nav"><a href="' . htmlspecialchars($this->moduleUrl) . '&amp;tfID=' . rawurlencode(($key . '.*')) . '">' . $GLOBALS['LANG']->getLL($getLLKey, TRUE) . '</a></p>
420 <p class="c-nav"><a href="' . htmlspecialchars($this->moduleUrl) . '">' . $GLOBALS['LANG']->getLL('goToToc', TRUE) . '</a></p>';
421 return $output;
422 }
423
424 /************************************
425 * Rendering CSH items
426 ************************************/
427
428 /**
429 * Make seeAlso links from $value
430 *
431 * @param string $value See-also input codes
432 * @param string $anchorTable If $anchorTable is set to a tablename, then references to this table will be made as anchors, not URLs.
433 * @return string See-also links HTML
434 */
435 public function make_seeAlso($value, $anchorTable = '') {
436 // Split references by comma or linebreak
437 $items = preg_split('/[,' . LF . ']/', $value);
438 $lines = array();
439 foreach ($items as $val) {
440 $val = trim($val);
441 if ($val) {
442 $iP = explode(':', $val);
443 $iPUrl = GeneralUtility::trimExplode('|', $val);
444 // URL reference:
445 if (substr($iPUrl[1], 0, 4) == 'http') {
446 $lines[] = '<a href="' . htmlspecialchars($iPUrl[1]) . '" target="_blank"><em>' . htmlspecialchars($iPUrl[0]) . '</em></a>';
447 } elseif (substr($iPUrl[1], 0, 5) == 'FILE:') {
448 $fileName = GeneralUtility::getFileAbsFileName(substr($iPUrl[1], 5), 1, 1);
449 if ($fileName && @is_file($fileName)) {
450 $fileName = '../' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($fileName);
451 $lines[] = '<a href="' . htmlspecialchars($fileName) . '" target="_blank"><em>' . htmlspecialchars($iPUrl[0]) . '</em></a>';
452 }
453 } else {
454 // "table" reference
455 if (!isset($GLOBALS['TCA'][$iP[0]]) || (!$iP[1] || is_array($GLOBALS['TCA'][$iP[0]]['columns'][$iP[1]])) && (!$this->limitAccess || $GLOBALS['BE_USER']->check('tables_select', $iP[0]) && (!$iP[1] || !$GLOBALS['TCA'][$iP[0]]['columns'][$iP[1]]['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $iP[0] . ':' . $iP[1])))) {
456 // Checking read access:
457 if (isset($GLOBALS['TCA_DESCR'][$iP[0]])) {
458 // Make see-also link:
459 $href = $this->renderALL || $anchorTable && $iP[0] == $anchorTable ? '#' . rawurlencode(implode('.', $iP)) : $this->moduleUrl . '&tfID=' . rawurlencode(implode('.', $iP)) . '&back=' . $this->tfID;
460 $label = $this->getTableFieldLabel($iP[0], $iP[1], ' / ');
461 $lines[] = '<a href="' . htmlspecialchars($href) . '">' . htmlspecialchars($label) . '</a>';
462 }
463 }
464 }
465 }
466 }
467 return implode('<br />', $lines);
468 }
469
470 /**
471 * Will return an image tag with description in italics.
472 *
473 * @param string $images Image file reference (list of)
474 * @param string $descr Description string (divided for each image by line break)
475 * @return string Image HTML codes
476 */
477 public function printImage($images, $descr) {
478 $code = '';
479 // Splitting:
480 $imgArray = GeneralUtility::trimExplode(',', $images, TRUE);
481 if (count($imgArray)) {
482 $descrArray = explode(LF, $descr, count($imgArray));
483 foreach ($imgArray as $k => $image) {
484 $descr = $descrArray[$k];
485 $absImagePath = GeneralUtility::getFileAbsFileName($image, 1, 1);
486 if ($absImagePath && @is_file($absImagePath)) {
487 $imgFile = \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($absImagePath);
488 $imgInfo = @getimagesize($absImagePath);
489 if (is_array($imgInfo)) {
490 $imgFile = '../' . $imgFile;
491 $code .= '<br /><img src="' . $imgFile . '" ' . $imgInfo[3] . ' class="c-inlineimg" alt="" /><br />
492 ';
493 $code .= '<p><em>' . htmlspecialchars($descr) . '</em></p>
494 ';
495 } else {
496 $code .= '<div style="background-color: red; border: 1px solid black; color: white;">NOT AN IMAGE: ' . $imgFile . '</div>';
497 }
498 } else {
499 $code .= '<div style="background-color: red; border: 1px solid black; color: white;">IMAGE FILE NOT FOUND: ' . $image . '</div>';
500 }
501 }
502 }
503 return $code;
504 }
505
506 /**
507 * Returns header HTML content
508 *
509 * @param string $str Header text
510 * @param integer $type Header type (1, 0)
511 * @return string The HTML for the header.
512 */
513 public function headerLine($str, $type = 0) {
514 switch ($type) {
515 case 1:
516 $str = '<h2 class="t3-row-header">' . htmlspecialchars($str) . '</h2>
517 ';
518 break;
519 case 0:
520 $str = '<h3 class="divider">' . htmlspecialchars($str) . '</h3>
521 ';
522 break;
523 }
524 return $str;
525 }
526
527 /**
528 * Returns prepared content
529 *
530 * @param string $str Content to format.
531 * @return string Formatted content.
532 */
533 public function prepareContent($str) {
534 return '<p>' . nl2br(trim(strip_tags($str, $this->allowedHTML))) . '</p>
535 ';
536 }
537
538 /**
539 * Prints a single $table/$field information piece
540 * If $anchors is set, then seeAlso references to the same table will be page-anchors, not links.
541 *
542 * @param string $key CSH key / table name
543 * @param string $field Sub key / field name
544 * @param bool $anchors If anchors is to be shown.
545 * @return string HTML content
546 */
547 public function printItem($key, $field, $anchors = FALSE) {
548 $out = '';
549 if ($key && (!$field || is_array($GLOBALS['TCA_DESCR'][$key]['columns'][$field]))) {
550 // Make seeAlso references.
551 $seeAlsoRes = $this->make_seeAlso($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['seeAlso'], $anchors ? $key : '');
552 // Making item:
553 $out = '<a name="' . $key . '.' . $field . '"></a>' . $this->headerLine($this->getTableFieldLabel($key, $field), 1) . $this->prepareContent($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['description']) . ($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['details'] ? $this->headerLine(($GLOBALS['LANG']->getLL('details') . ':')) . $this->prepareContent($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['details']) : '') . ($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['syntax'] ? $this->headerLine(($GLOBALS['LANG']->getLL('syntax') . ':')) . $this->prepareContent($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['syntax']) : '') . ($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['image'] ? $this->printImage($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['image'], $GLOBALS['TCA_DESCR'][$key]['columns'][$field]['image_descr']) : '') . ($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['seeAlso'] && $seeAlsoRes ? $this->headerLine(($GLOBALS['LANG']->getLL('seeAlso') . ':')) . '<p>' . $seeAlsoRes . '</p>' : '') . ($this->back ? '<br /><p><a href="' . htmlspecialchars($this->moduleUrl . '&amp;tfID=' . rawurlencode($this->back)) . '" class="typo3-goBack">' . htmlspecialchars($GLOBALS['LANG']->getLL('goBack')) . '</a></p>' : '') . '<br />';
554 }
555 return $out;
556 }
557
558 /**
559 * Returns labels for a given field in a given structure
560 *
561 * @param string $key CSH key / table name
562 * @param string $field Sub key / field name
563 * @return array Table and field labels in a numeric array
564 */
565 public function getTableFieldNames($key, $field) {
566 $GLOBALS['LANG']->loadSingleTableDescription($key);
567 // Define the label for the key
568 $keyName = $key;
569 if (is_array($GLOBALS['TCA_DESCR'][$key]['columns']['']) && isset($GLOBALS['TCA_DESCR'][$key]['columns']['']['alttitle'])) {
570 // If there's an alternative title, use it
571 $keyName = $GLOBALS['TCA_DESCR'][$key]['columns']['']['alttitle'];
572 } elseif (isset($GLOBALS['TCA'][$key])) {
573 // Otherwise, if it's a table, use its title
574 $keyName = $GLOBALS['TCA'][$key]['ctrl']['title'];
575 } else {
576 // If no title was found, make sure to remove any "_MOD_"
577 $keyName = preg_replace('/^_MOD_/', '', $key);
578 }
579 // Define the label for the field
580 $fieldName = $field;
581 if (is_array($GLOBALS['TCA_DESCR'][$key]['columns'][$field]) && isset($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['alttitle'])) {
582 // If there's an alternative title, use it
583 $fieldName = $GLOBALS['TCA_DESCR'][$key]['columns'][$field]['alttitle'];
584 } elseif (isset($GLOBALS['TCA'][$key]) && isset($GLOBALS['TCA'][$key]['columns'][$field])) {
585 // Otherwise, if it's a table, use its title
586 $fieldName = $GLOBALS['TCA'][$key]['columns'][$field]['label'];
587 }
588 return array($keyName, $fieldName);
589 }
590
591 /**
592 * Returns composite label for table/field
593 *
594 * @param string $key CSH key / table name
595 * @param string $field Sub key / field name
596 * @param string $mergeToken Token to merge the two strings with
597 * @return string Labels joined with merge token
598 * @see getTableFieldNames()
599 */
600 public function getTableFieldLabel($key, $field = '', $mergeToken = ': ') {
601 // Get table / field parts:
602 list($tableName, $fieldName) = $this->getTableFieldNames($key, $field);
603 // Create label:
604 $labelString = $GLOBALS['LANG']->sL($tableName) . ($field ? $mergeToken . rtrim(trim($GLOBALS['LANG']->sL($fieldName)), ':') : '');
605 return $labelString;
606 }
607
608 /******************************
609 * Glossary related
610 ******************************/
611
612 /**
613 * Creates glossary index in $this->glossaryWords
614 * Glossary is cached in cache_hash cache and so will be updated only when cache is cleared.
615 *
616 * @return void
617 */
618 public function createGlossaryIndex() {
619 // Create hash string and try to retrieve glossary array:
620 $hash = md5('help_cshmanual:glossary');
621 $cachedData = BackendUtility::getHash($hash);
622 // Generate glossary words if not found:
623 if (is_array($cachedData)) {
624 list($this->glossaryWords, $this->substWords) = $cachedData;
625 } else {
626 // Initialize:
627 $this->glossaryWords = array();
628 $this->substWords = array();
629 $CSHkeys = array_flip(array_keys($GLOBALS['TCA_DESCR']));
630 // Glossary
631 foreach ($CSHkeys as $cshKey => $value) {
632 if (GeneralUtility::isFirstPartOfStr($cshKey, 'xGLOSSARY_') && !isset($GLOBALS['TCA'][$cshKey])) {
633 $GLOBALS['LANG']->loadSingleTableDescription($cshKey);
634 if (is_array($GLOBALS['TCA_DESCR'][$cshKey]['columns'])) {
635 // Traverse table columns as listed in TCA_DESCR
636 foreach ($GLOBALS['TCA_DESCR'][$cshKey]['columns'] as $field => $data) {
637 if ($field) {
638 $this->glossaryWords[$cshKey . '.' . $field] = array(
639 'title' => trim($data['alttitle'] ?: $cshKey),
640 'description' => str_replace('%22', '%23%23%23', rawurlencode($data['description']))
641 );
642 }
643 }
644 }
645 }
646 }
647 // First, create unique list of words:
648 foreach ($this->glossaryWords as $key => $value) {
649 // Making word lowercase in order to filter out same words in different cases.
650 $word = strtolower($value['title']);
651 if ($word !== '') {
652 $this->substWords[$word] = $value;
653 $this->substWords[$word]['key'] = $key;
654 }
655 }
656 krsort($this->substWords);
657 BackendUtility::storeHash($hash, array($this->glossaryWords, $this->substWords), 'Glossary');
658 }
659 }
660
661 /**
662 * Processing of all non-HTML content in the output
663 * Will be done by a call-back to ->substituteGlossaryWords_htmlcleaner_callback()
664 *
665 * @param string $code Input HTML code
666 * @return string Output HTML code
667 */
668 public function substituteGlossaryWords($code) {
669 $htmlParser = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\HtmlParser');
670 $htmlParser->pObj = $this;
671 $code = $htmlParser->HTMLcleaner($code, array(), 1);
672 return $code;
673 }
674
675 /**
676 * Substituting glossary words in the CSH
677 * This is a call-back function from "class local_t3lib_parsehtml
678 * extends \TYPO3\CMS\Core\Html\HtmlParser", see top of this script
679 *
680 * @param string $code Input HTML string
681 * @return string HTML with substituted words in.
682 */
683 public function substituteGlossaryWords_htmlcleaner_callback($code) {
684 if (is_array($this->substWords) && count($this->substWords) && strlen(trim($code))) {
685 // Substitute words:
686 foreach ($this->substWords as $wordKey => $wordSet) {
687 // quoteMeta used so special chars (which should not occur though) in words will not break the regex. Seemed to work (- kasper)
688 $parts = preg_split('/( |[\\(])(' . quoteMeta($wordSet['title']) . ')([\\.\\!\\)\\?\\:\\,]+| )/i', ' ' . $code . ' ', 2, PREG_SPLIT_DELIM_CAPTURE);
689 if (count($parts) == 5) {
690 $parts[2] = '<a class="glossary-term" href="' . htmlspecialchars($this->moduleUrl . '&amp;tfID=' . rawurlencode($wordSet['key']) . '&amp;back=' . $this->tfID) . '" title="' . rawurlencode(htmlspecialchars(GeneralUtility::fixed_lgd_cs(rawurldecode($wordSet['description']), 80))) . '">' . htmlspecialchars($parts[2]) . '</a>';
691 $code = substr(implode('', $parts), 1, -1);
692 // Disable entry so it doesn't get used next time:
693 unset($this->substWords[$wordKey]);
694 }
695 }
696 $code = str_replace('###', '&quot;', rawurldecode($code));
697 }
698 return $code;
699 }
700
701 }