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