addd26d570d413498b26736ce017c5064d543065
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Classes / Controller / BrowseLinksController.php
1 <?php
2 namespace TYPO3\CMS\Rtehtmlarea\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 Psr\Http\Message\ServerRequestInterface;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Page\PageRenderer;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Lang\LanguageService;
22 use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController;
23
24 /**
25 * Extended controller for link browser
26 */
27 class BrowseLinksController extends AbstractLinkBrowserController
28 {
29 /**
30 * Active with TYPO3 Element Browser: Contains the name of the form field for which this window
31 * opens - thus allows us to make references back to the main window in which the form is.
32 * Example value: "data[pages][39][bodytext]|||tt_content|"
33 * or "data[tt_content][NEW3fba56fde763d][image]|||gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai|"
34 *
35 * Values:
36 * 0: form field name reference, eg. "data[tt_content][123][image]"
37 * 1: htmlArea RTE parameters: editorNo:contentTypo3Language
38 * 2: RTE config parameters: RTEtsConfigParams
39 * 3: allowed types. Eg. "tt_content" or "gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai"
40 *
41 * $pArr = explode('|', $this->bparams);
42 * $formFieldName = $pArr[0];
43 * $allowedTablesOrFileTypes = $pArr[3];
44 *
45 * @var string
46 */
47 protected $bparams;
48
49 /**
50 * @var int
51 */
52 protected $editorNo;
53
54 /**
55 * TYPO3 language code of the content language
56 *
57 * @var int
58 */
59 protected $contentTypo3Language;
60
61 /**
62 * Language service object for localization to the content language
63 *
64 * @var LanguageService
65 */
66 protected $contentLanguageService;
67
68 /**
69 * @var array
70 */
71 protected $buttonConfig = [];
72
73 /**
74 * @var array
75 */
76 protected $thisConfig = [];
77
78 /**
79 * RTE configuration
80 *
81 * @var array
82 */
83 protected $RTEProperties = [];
84
85 /**
86 * Used with the Rich Text Editor.
87 * Example value: "tt_content:NEW3fba58c969f5c:bodytext:23:text:23:"
88 *
89 * @var string
90 */
91 protected $RTEtsConfigParams;
92
93 /**
94 * @var array
95 */
96 protected $classesAnchorDefault = array();
97
98 /**
99 * @var array
100 */
101 protected $classesAnchorDefaultTitle = array();
102
103 /**
104 * @var array
105 */
106 protected $classesAnchorClassTitle = array();
107
108 /**
109 * @var array
110 */
111 protected $classesAnchorDefaultTarget = array();
112
113 /**
114 * @var array
115 */
116 protected $classesAnchorJSOptions = array();
117
118 /**
119 * @var string
120 */
121 protected $defaultLinkTarget = '';
122
123 /**
124 * @var array
125 */
126 protected $additionalAttributes = array();
127
128 /**
129 * @var string
130 */
131 protected $siteUrl = '';
132
133 /**
134 * Initialize controller
135 */
136 protected function init()
137 {
138 parent::init();
139
140 $lang = $this->getLanguageService();
141 $lang->includeLLFile('EXT:rtehtmlarea/Resources/Private/Language/locallang_browselinkscontroller.xlf');
142 $lang->includeLLFile('EXT:rtehtmlarea/Resources/Private/Language/locallang_dialogs.xlf');
143
144 $this->contentLanguageService = GeneralUtility::makeInstance(LanguageService::class);
145 }
146
147 /**
148 * @param ServerRequestInterface $request
149 */
150 protected function initVariables(ServerRequestInterface $request)
151 {
152 parent::initVariables($request);
153
154 $queryParameters = $request->getQueryParams();
155 $this->bparams = isset($queryParameters['bparams']) ? $queryParameters['bparams'] : '';
156
157 $this->siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
158
159 $currentLinkParts = isset($queryParameters['curUrl']) ? $queryParameters['curUrl'] : [];
160 if (isset($currentLinkParts['all'])) {
161 $currentLinkParts = GeneralUtility::get_tag_attributes($queryParameters['curUrl']['all']);
162 $currentLinkParts['url'] = htmlspecialchars_decode($currentLinkParts['href']);
163 unset($currentLinkParts['href']);
164 }
165 $this->currentLinkParts = $currentLinkParts;
166
167 // Process bparams
168 $pArr = explode('|', $this->bparams);
169 $pRteArr = explode(':', $pArr[1]);
170 $this->editorNo = $pRteArr[0];
171 $this->contentTypo3Language = $pRteArr[1];
172 $this->RTEtsConfigParams = $pArr[2];
173 if (!$this->editorNo) {
174 $this->editorNo = GeneralUtility::_GP('editorNo');
175 $this->contentTypo3Language = GeneralUtility::_GP('contentTypo3Language');
176 $this->RTEtsConfigParams = GeneralUtility::_GP('RTEtsConfigParams');
177 }
178 $pArr[1] = implode(':', array($this->editorNo, $this->contentTypo3Language));
179 $pArr[2] = $this->RTEtsConfigParams;
180 $this->bparams = implode('|', $pArr);
181
182 $this->contentLanguageService->init($this->contentTypo3Language);
183
184 $RTEtsConfigParts = explode(':', $this->RTEtsConfigParams);
185 $RTEsetup = $this->getBackendUser()->getTSConfig('RTE', BackendUtility::getPagesTSconfig($RTEtsConfigParts[5]));
186 $this->RTEProperties = $RTEsetup['properties'];
187
188 $this->thisConfig = BackendUtility::RTEsetup($this->RTEProperties, $RTEtsConfigParts[0], $RTEtsConfigParts[2], $RTEtsConfigParts[4]);
189 $this->buttonConfig = isset($this->thisConfig['buttons.']['link.'])
190 ? $this->thisConfig['buttons.']['link.']
191 : [];
192 }
193
194 /**
195 * Initialize document template object
196 *
197 * @return void
198 */
199 protected function initDocumentTemplate()
200 {
201 parent::initDocumentTemplate();
202
203 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
204 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Rtehtmlarea/RteLinkBrowser');
205 }
206
207 /**
208 * Initialize $this->currentLink and $this->currentLinkHandler
209 *
210 * @return void
211 */
212 protected function initCurrentUrl()
213 {
214 if (empty($this->currentLinkParts)) {
215 return;
216 }
217
218 if (!empty($this->currentLinkParts['class'])) {
219 // remove required classes
220 $currentClasses = GeneralUtility::trimExplode(' ', $this->currentLinkParts['class'], true);
221 if (count($currentClasses) > 1) {
222 $this->currentLinkParts['class'] = end($currentClasses);
223 }
224 }
225
226 if (empty($this->currentLinkParts['data-htmlarea-external']) && strpos($this->currentLinkParts['url'], 'mailto:') === false) {
227 // strip siteUrl prefix except for external and mail links
228 $paramsPosition = strpos($this->currentLinkParts['url'], '?');
229 if ($paramsPosition !== false) {
230 $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $paramsPosition + 1);
231 }
232 // special treatment for page links, remove the id= part
233 $idPosition = strpos($this->currentLinkParts['url'], 'id=');
234 if ($idPosition !== false) {
235 $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $idPosition + 3);
236 }
237
238 // in RTE the additional params are encoded directly at the end of the href part
239 // we need to split this again into dedicated fields
240 $additionalParamsPosition = strpos($this->currentLinkParts['url'], '?');
241 if ($additionalParamsPosition === false) {
242 $additionalParamsPosition = strpos($this->currentLinkParts['url'], '&');
243 }
244 if ($additionalParamsPosition !== false) {
245 $this->currentLinkParts['params'] = substr($this->currentLinkParts['url'], $additionalParamsPosition);
246 $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], 0, $additionalParamsPosition);
247 // in case the first sign was an ? override it with &
248 $this->currentLinkParts['params'][0] = '&';
249 }
250 }
251
252 parent::initCurrentUrl();
253 }
254
255 /**
256 * Renders the link attributes for the selected link handler
257 *
258 * @return string
259 */
260 public function renderLinkAttributeFields()
261 {
262 // Processing the classes configuration
263 if (!empty($this->buttonConfig['properties.']['class.']['allowedClasses'])) {
264 $classesAnchorArray = GeneralUtility::trimExplode(',', $this->buttonConfig['properties.']['class.']['allowedClasses'], true);
265 // Collecting allowed classes and configured default values
266 $classesAnchor = [
267 'all' => []
268 ];
269 $titleReadOnly = $this->buttonConfig['properties.']['title.']['readOnly']
270 || $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly'];
271 if (is_array($this->RTEProperties['classesAnchor.'])) {
272 foreach ($this->RTEProperties['classesAnchor.'] as $label => $conf) {
273 if (in_array($conf['class'], $classesAnchorArray)) {
274 $classesAnchor['all'][] = $conf['class'];
275 if ($conf['type'] === $this->displayedLinkHandlerId) {
276 $classesAnchor[$conf['type']][] = $conf['class'];
277 if ($this->buttonConfig[$conf['type'] . '.']['properties.']['class.']['default'] == $conf['class']) {
278 $this->classesAnchorDefault[$conf['type']] = $conf['class'];
279 if ($conf['titleText']) {
280 $this->classesAnchorDefaultTitle[$conf['type']] = $this->contentLanguageService->sL(trim($conf['titleText']));
281 }
282 if (isset($conf['target'])) {
283 $this->classesAnchorDefaultTarget[$conf['type']] = trim($conf['target']);
284 }
285 }
286 }
287 if ($titleReadOnly && $conf['titleText']) {
288 $this->classesAnchorClassTitle[$conf['class']] = ($this->classesAnchorDefaultTitle[$conf['type']] = $this->contentLanguageService->sL(trim($conf['titleText'])));
289 }
290 }
291 }
292 }
293 if (isset($this->linkAttributeValues['class'])
294 && isset($classesAnchor[$this->displayedLinkHandlerId])
295 && !in_array($this->linkAttributeValues['class'], $classesAnchor[$this->displayedLinkHandlerId], true)
296 ) {
297 unset($this->linkAttributeValues['class']);
298 }
299 // Constructing the class selector options
300 foreach ($classesAnchorArray as $class) {
301 if (!in_array($class, $classesAnchor['all']) || in_array($class, $classesAnchor['all']) && is_array($classesAnchor[$this->displayedLinkHandlerId]) && in_array($class, $classesAnchor[$this->displayedLinkHandlerId])) {
302 $selected = '';
303 if ($this->linkAttributeValues['class'] === $class || !$this->linkAttributeValues['class'] && $this->classesAnchorDefault[$this->displayedLinkHandlerId] == $class) {
304 $selected = 'selected="selected"';
305 }
306 $classLabel = !empty($this->RTEProperties['classes.'][$class . '.']['name'])
307 ? $this->getPageConfigLabel($this->RTEProperties['classes.'][$class . '.']['name'], 0)
308 : $class;
309 $classStyle = !empty($this->RTEProperties['classes.'][$class . '.']['value'])
310 ? $this->RTEProperties['classes.'][$class . '.']['value']
311 : '';
312 $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] .= '<option ' . $selected . ' value="' . $class . '"' . ($classStyle ? ' style="' . $classStyle . '"' : '') . '>' . $classLabel . '</option>';
313 }
314 }
315 if ($this->classesAnchorJSOptions[$this->displayedLinkHandlerId] && !($this->buttonConfig['properties.']['class.']['required'] || $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['class.']['required'])) {
316 $selected = '';
317 if (!$this->linkAttributeValues['class'] && !$this->classesAnchorDefault[$this->displayedLinkHandlerId]) {
318 $selected = 'selected="selected"';
319 }
320 $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] = '<option ' . $selected . ' value=""></option>' . $this->classesAnchorJSOptions[$this->displayedLinkHandlerId];
321 }
322 }
323 // Default target
324 $this->defaultLinkTarget = $this->classesAnchorDefault[$this->displayedLinkHandlerId] && $this->classesAnchorDefaultTarget[$this->displayedLinkHandlerId]
325 ? $this->classesAnchorDefaultTarget[$this->displayedLinkHandlerId]
326 : (isset($this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['target.']['default'])
327 ? $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['target.']['default']
328 : (isset($this->buttonConfig['properties.']['target.']['default'])
329 ? $this->buttonConfig['properties.']['target.']['default']
330 : ''));
331 // Initializing additional attributes
332 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rtehtmlarea']['plugins']['TYPO3Link']['additionalAttributes']) {
333 $addAttributes = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rtehtmlarea']['plugins']['TYPO3Link']['additionalAttributes'], true);
334 foreach ($addAttributes as $attribute) {
335 $this->additionalAttributes[$attribute] = isset($this->linkAttributeValues[$attribute]) ? $this->linkAttributeValues[$attribute] : '';
336 }
337 }
338 return parent::renderLinkAttributeFields();
339 }
340
341 /**
342 * Localize a label obtained from Page TSConfig
343 *
344 * @param string $string The label to be localized
345 * @param bool $JScharCode If needs to be converted to an array of char numbers
346 * @return string Localized string
347 */
348 public function getPageConfigLabel($string, $JScharCode = true)
349 {
350 if (substr($string, 0, 4) !== 'LLL:') {
351 $label = $string;
352 } else {
353 $label = $this->getLanguageService()->sL(trim($string));
354 }
355 $label = str_replace('"', '\\"', str_replace('\\\'', '\'', $label));
356 return $JScharCode ? GeneralUtility::quoteJSvalue($label) : $label;
357 }
358
359 /**
360 * @return string
361 */
362 protected function renderCurrentUrl()
363 {
364 $removeLink = '<a href="#" class="btn btn-default t3js-removeCurrentLink">' . $this->getLanguageService()->getLL('removeLink', true) . '</a>';
365 return '
366 <table border="0" cellpadding="0" cellspacing="0" id="typo3-curUrl">
367 <tr>
368 <td>' . $this->getLanguageService()->getLL('currentLink', true) . ': ' . htmlspecialchars($this->currentLinkHandler->formatCurrentUrl()) . $removeLink . '</td>
369 </tr>
370 </table>';
371 }
372
373 /**
374 * Get the allowed items or tabs
375 *
376 * @return string[]
377 */
378 protected function getAllowedItems()
379 {
380 $allowedItems = parent::getAllowedItems();
381
382 $blindLinkOptions = isset($this->thisConfig['blindLinkOptions'])
383 ? GeneralUtility::trimExplode(',', $this->thisConfig['blindLinkOptions'], true)
384 : [];
385 $allowedItems = array_diff($allowedItems, $blindLinkOptions);
386
387 if (is_array($this->buttonConfig['options.']) && $this->buttonConfig['options.']['removeItems']) {
388 $allowedItems = array_diff($allowedItems, GeneralUtility::trimExplode(',', $this->buttonConfig['options.']['removeItems'], true));
389 }
390
391 return $allowedItems;
392 }
393
394 /**
395 * Get the allowed link attributes
396 *
397 * @return string[]
398 */
399 protected function getAllowedLinkAttributes()
400 {
401 $allowedLinkAttributes = parent::getAllowedLinkAttributes();
402
403 $blindLinkFields = isset($this->thisConfig['blindLinkFields'])
404 ? GeneralUtility::trimExplode(',', $this->thisConfig['blindLinkFields'], true)
405 : [];
406 $allowedLinkAttributes = array_diff($allowedLinkAttributes, $blindLinkFields);
407
408 return $allowedLinkAttributes;
409 }
410
411 /**
412 * Create an array of link attribute field rendering definitions
413 *
414 * @return string[]
415 */
416 protected function getLinkAttributeFieldDefinitions()
417 {
418 $fieldRenderingDefinitions = parent::getLinkAttributeFieldDefinitions();
419 $fieldRenderingDefinitions['title'] = $this->getTitleField();
420 $fieldRenderingDefinitions['class'] = $this->getClassField();
421 $fieldRenderingDefinitions['target'] = $this->getTargetField();
422 $fieldRenderingDefinitions['rel'] = $this->getRelField();
423 if (empty($this->buttonConfig['queryParametersSelector.']['enabled'])) {
424 unset($fieldRenderingDefinitions['params']);
425 }
426 return $fieldRenderingDefinitions;
427 }
428
429 /**
430 * Add rel field
431 *
432 * @return string
433 */
434 protected function getRelField()
435 {
436 if (empty($this->buttonConfig['relAttribute.']['enabled'])) {
437 return '';
438 }
439 // @todo add rel to attributes
440 $currentRel = $this->displayedLinkHandler === $this->currentLinkHandler && !empty($this->currentLinkParts)
441 ? $this->linkAttributeValues['rel']
442 : '';
443 // @todo define label "linkRelationship" below in xlf
444 return '
445 <form action="" name="lrelform" id="lrelform" class="t3js-dummyform">
446 <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkRel">
447 <tr>
448 <td><label>' . $this->getLanguageService()->getLL('linkRelationship', true) . ':</label></td>
449 <td colspan="3"><input type="text" name="lrel" value="' . $currentRel . '" /></td>
450 </tr>
451 </table>
452 </form>
453 ';
454 }
455
456 /**
457 * Add target selector
458 *
459 * @return string
460 */
461 protected function getTargetField()
462 {
463 $targetSelectorConfig = array();
464 if (is_array($this->buttonConfig['targetSelector.'])) {
465 $targetSelectorConfig = $this->buttonConfig['targetSelector.'];
466 }
467 $target = $this->linkAttributeValues['target'] ?: $this->defaultLinkTarget;
468 $lang = $this->getLanguageService();
469 $targetSelector = '';
470
471 if (!$targetSelectorConfig['disabled']) {
472 $targetSelector = '
473 <select name="ltarget_type" class="t3js-targetPreselect">
474 <option value=""></option>
475 <option value="_top">' . $lang->getLL('top', true) . '</option>
476 <option value="_blank">' . $lang->getLL('newWindow', true) . '</option>
477 </select>
478 ';
479 }
480
481 return '
482 <form action="" name="ltargetform" id="ltargetform" class="t3js-dummyform">
483 <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTarget">
484 <tr' . ($targetSelectorConfig['disabled'] ? ' style="display: none;"' : '') . '>
485 <td style="width: 96px;">' . $lang->getLL('target', true) . ':</td>
486 <td>
487 <input type="text" name="ltarget" class="t3js-linkTarget" value="' . htmlspecialchars($target) . '" />
488 ' . $targetSelector . '
489 </td>
490 </tr>
491 </table>
492 </form>
493 ';
494 }
495
496 /**
497 * Add title selector
498 *
499 * @return string
500 */
501 protected function getTitleField()
502 {
503 if ($this->linkAttributeValues['title']) {
504 $title = $this->linkAttributeValues['title'];
505 } else {
506 $title = !$this->classesAnchorDefault[$this->displayedLinkHandlerId] ? '' : $this->classesAnchorDefaultTitle[$this->displayedLinkHandlerId];
507 }
508 if (isset($this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly'])) {
509 $readOnly = (bool)$this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly'];
510 } else {
511 $readOnly = isset($this->buttonConfig['properties.']['title.']['readOnly'])
512 ? (bool)$this->buttonConfig['properties.']['title.']['readOnly']
513 : false;
514 }
515
516 if ($readOnly) {
517 $currentClass = $this->linkAttributeFields['class'];
518 if (!$currentClass) {
519 $currentClass = empty($this->classesAnchorDefault[$this->displayedLinkHandlerId]) ? '' : $this->classesAnchorDefault[$this->displayedLinkHandlerId];
520 }
521 $title = $currentClass
522 ? $this->classesAnchorClassTitle[$currentClass]
523 : $this->classesAnchorDefaultTitle[$this->displayedLinkHandlerId];
524 }
525 return '
526 <form action="" name="ltitleform" id="ltitleform" class="t3js-dummyform">
527 <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTitle">
528 <tr>
529 <td style="width: 96px;"><label for="rtehtmlarea-browse-links-anchor_title" id="rtehtmlarea-browse-links-title-label">' . $this->getLanguageService()->getLL('anchor_title', true) . '</label></td>
530 <td>
531 <span id="rtehtmlarea-browse-links-title-input" style="display: ' . ($readOnly ? 'none' : 'inline') . ';">
532 <input type="text" id="rtehtmlarea-browse-links-anchor_title" name="ltitle" value="' . htmlspecialchars($title) . '" />
533 </span>
534 <span id="rtehtmlarea-browse-links-title-readonly" style="display: ' . ($readOnly ? 'inline' : 'none') . ';">' . htmlspecialchars($title) . '</span>
535 </td>
536 </tr>
537 </table>
538 </form>
539 ';
540 }
541
542 /**
543 * Return html code for the class selector
544 *
545 * @return string the html code to be added to the form
546 */
547 protected function getClassField()
548 {
549 $selectClass = '';
550 if ($this->classesAnchorJSOptions[$this->displayedLinkHandlerId]) {
551 $selectClass = '
552 <form action="" name="lclassform" id="lclassform" class="t3js-dummyform">
553 <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkClass">
554 <tr>
555 <td style="width: 96px;">' . $this->getLanguageService()->getLL('anchor_class', true) . '</td>
556 <td><select name="lclass" class="t3js-class-selector">
557 ' . $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] . '
558 </select></td>
559 </tr>
560 </table>
561 </form>
562 ';
563 }
564 return $selectClass;
565 }
566
567 /**
568 * Return the ID of current page
569 *
570 * @return int
571 */
572 protected function getCurrentPageId()
573 {
574 return explode(':', $this->RTEtsConfigParams)[5];
575 }
576
577 /**
578 * Retrieve the configuration
579 *
580 * This is only used by RTE currently.
581 *
582 * @return array
583 */
584 public function getConfiguration()
585 {
586 return $this->buttonConfig;
587 }
588
589 /**
590 * Get attributes for the body tag
591 *
592 * @return string[] Array of body-tag attributes
593 */
594 protected function getBodyTagAttributes()
595 {
596 $parameters = parent::getBodyTagAttributes();
597 $parameters['data-site-url'] = $this->siteUrl;
598 return $parameters;
599 }
600
601 /**
602 * @param array $overrides
603 *
604 * @return array Array of parameters which have to be added to URLs
605 */
606 public function getUrlParameters(array $overrides = null)
607 {
608 return [
609 'act' => isset($overrides['act']) ? $overrides['act'] : $this->displayedLinkHandlerId,
610 'bparams' => $this->bparams,
611 'editorNo' => $this->editorNo,
612 'contentTypo3Language' => $this->contentTypo3Language
613 ];
614 }
615 }