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