<?php namespace SGalinski\Tinymce4Rte\Controller; /* * This file is part of the TYPO3 CMS project. * * It is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, either version 2 * of the License, or any later version. * * For the full copyright and license information, please read the * LICENSE.txt file that was distributed with this source code. * * The TYPO3 project - inspiring people to share! */ use Psr\Http\Message\ServerRequestInterface; use SGalinski\Tinymce4Rte\Utility\VersionUtility; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Lang\LanguageService; use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController; /** * Extended controller for link browser */ class BrowseLinksController extends AbstractLinkBrowserController { /** * Active with TYPO3 Element Browser: Contains the name of the form field for which this window * opens - thus allows us to make references back to the main window in which the form is. * Example value: "data[pages][39][bodytext]|||tt_content|" * or "data[tt_content][NEW3fba56fde763d][image]|||gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai|" * * Values: * 0: form field name reference, eg. "data[tt_content][123][image]" * 1: htmlArea RTE parameters: editorNo:contentTypo3Language * 2: RTE config parameters: RTEtsConfigParams * 3: allowed types. Eg. "tt_content" or "gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai" * * $pArr = explode('|', $this->bparams); * $formFieldName = $pArr[0]; * $allowedTablesOrFileTypes = $pArr[3]; * * @var string */ protected $bparams; /** * @var int */ protected $editorNo; /** * TYPO3 language code of the content language * * @var int */ protected $contentTypo3Language; /** * Language service object for localization to the content language * * @var LanguageService */ protected $contentLanguageService; /** * @var array */ protected $buttonConfig = []; /** * @var array */ protected $thisConfig = []; /** * RTE configuration * * @var array */ protected $RTEProperties = []; /** * Used with the Rich Text Editor. * Example value: "tt_content:NEW3fba58c969f5c:bodytext:23:text:23:" * * @var string */ protected $RTEtsConfigParams; /** * @var array */ protected $classesAnchorDefault = array(); /** * @var array */ protected $classesAnchorDefaultTitle = array(); /** * @var array */ protected $classesAnchorClassTitle = array(); /** * @var array */ protected $classesAnchorDefaultTarget = array(); /** * @var array */ protected $classesAnchorJSOptions = array(); /** * @var string */ protected $defaultLinkTarget = ''; /** * @var array */ protected $additionalAttributes = array(); /** * @var string */ protected $siteUrl = ''; /** * Initialize controller */ protected function init() { parent::init(); $lang = $this->getLanguageService(); $lang->includeLLFile('EXT:tinymce4_rte/Resources/Private/Language/locallang_browselinkscontroller.xlf'); $lang->includeLLFile('EXT:tinymce4_rte/Resources/Private/Language/locallang_dialogs.xlf'); $this->contentLanguageService = GeneralUtility::makeInstance(LanguageService::class); } /** * @param ServerRequestInterface $request */ protected function initVariables(ServerRequestInterface $request) { parent::initVariables($request); $queryParameters = $request->getQueryParams(); $this->bparams = isset($queryParameters['bparams']) ? $queryParameters['bparams'] : ''; $this->siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); $currentLinkParts = isset($queryParameters['curUrl']) ? $queryParameters['curUrl'] : []; if (isset($currentLinkParts['all'])) { $currentLinkParts = GeneralUtility::get_tag_attributes($queryParameters['curUrl']['all']); $currentLinkParts['url'] = htmlspecialchars_decode($currentLinkParts['href']); unset($currentLinkParts['href']); } $this->currentLinkParts = $currentLinkParts; // Process bparams $pArr = explode('|', $this->bparams); $pRteArr = explode(':', $pArr[1]); $this->editorNo = $pRteArr[0]; $this->contentTypo3Language = $pRteArr[1]; $this->RTEtsConfigParams = $pArr[2]; if (!$this->editorNo) { $this->editorNo = GeneralUtility::_GP('editorNo'); $this->contentTypo3Language = GeneralUtility::_GP('contentTypo3Language'); $this->RTEtsConfigParams = GeneralUtility::_GP('RTEtsConfigParams'); } $pArr[1] = implode(':', array($this->editorNo, $this->contentTypo3Language)); $pArr[2] = $this->RTEtsConfigParams; $this->bparams = implode('|', $pArr); $this->contentLanguageService->init($this->contentTypo3Language); $RTEtsConfigParts = explode(':', $this->RTEtsConfigParams); $RTEsetup = $this->getBackendUser()->getTSConfig('RTE', BackendUtility::getPagesTSconfig($RTEtsConfigParts[5])); $this->RTEProperties = $RTEsetup['properties']; $this->thisConfig = BackendUtility::RTEsetup($this->RTEProperties, $RTEtsConfigParts[0], $RTEtsConfigParts[2], $RTEtsConfigParts[4]); $this->buttonConfig = isset($this->thisConfig['buttons.']['link.']) ? $this->thisConfig['buttons.']['link.'] : []; } /** * Initialize document template object * * @return void */ protected function initDocumentTemplate() { parent::initDocumentTemplate(); $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Tinymce4Rte/RteLinkBrowser'); } /** * Initialize $this->currentLink and $this->currentLinkHandler * * @return void */ protected function initCurrentUrl() { if (empty($this->currentLinkParts)) { return; } if (VersionUtility::isVersion870OrHigher()) { if (!empty($this->currentLinkParts['url'])) { /** @noinspection ExceptionsAnnotatingAndHandlingInspection */ /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ $linkService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\LinkHandling\LinkService::class); $data = $linkService->resolve($this->currentLinkParts['url']); $this->currentLinkParts['type'] = $data['type']; unset($data['type']); $this->currentLinkParts['url'] = $data; } if (!empty($this->currentLinkParts['class'])) { // Only keep last class value (others are automatically added again by required option) // https://review.typo3.org/#/c/29643 $currentClasses = GeneralUtility::trimExplode(' ', $this->currentLinkParts['class'], true); if (count($currentClasses) > 1) { $this->currentLinkParts['class'] = end($currentClasses); } } } else { if (!empty($this->currentLinkParts['class'])) { // remove required classes $currentClasses = GeneralUtility::trimExplode(' ', $this->currentLinkParts['class'], TRUE); if (count($currentClasses) > 1) { $this->currentLinkParts['class'] = end($currentClasses); } } if (empty($this->currentLinkParts['data-htmlarea-external'])) { // strip siteUrl prefix except for external and mail links if (strpos($this->currentLinkParts['url'], 'mailto:') === FALSE) { $paramsPosition = strpos($this->currentLinkParts['url'], '?'); if ($paramsPosition !== FALSE) { $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $paramsPosition + 1); } } // special treatment for page links, remove the id= part $idPosition = strpos($this->currentLinkParts['url'], 'id='); if ($idPosition !== FALSE) { $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], $idPosition + 3); } // in RTE the additional params are encoded directly at the end of the href part // we need to split this again into dedicated fields $additionalParamsPosition = strpos($this->currentLinkParts['url'], '?'); if ($additionalParamsPosition === FALSE) { $additionalParamsPosition = strpos($this->currentLinkParts['url'], '&'); } if ($additionalParamsPosition !== FALSE) { $this->currentLinkParts['params'] = substr($this->currentLinkParts['url'], $additionalParamsPosition); $this->currentLinkParts['url'] = substr($this->currentLinkParts['url'], 0, $additionalParamsPosition); // in case the first sign was an ? override it with & $this->currentLinkParts['params'][0] = '&'; } } } parent::initCurrentUrl(); } /** * Renders the link attributes for the selected link handler * * @return string */ public function renderLinkAttributeFields() { // Processing the classes configuration if (!empty($this->buttonConfig['properties.']['class.']['allowedClasses'])) { $classesAnchorArray = GeneralUtility::trimExplode(',', $this->buttonConfig['properties.']['class.']['allowedClasses'], true); // Collecting allowed classes and configured default values $classesAnchor = [ 'all' => [] ]; $titleReadOnly = $this->buttonConfig['properties.']['title.']['readOnly'] || $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly']; if (is_array($this->RTEProperties['classesAnchor.'])) { foreach ($this->RTEProperties['classesAnchor.'] as $label => $conf) { if (in_array($conf['class'], $classesAnchorArray)) { $classesAnchor['all'][] = $conf['class']; if ($conf['type'] === $this->displayedLinkHandlerId) { $classesAnchor[$conf['type']][] = $conf['class']; if ($this->buttonConfig[$conf['type'] . '.']['properties.']['class.']['default'] == $conf['class']) { $this->classesAnchorDefault[$conf['type']] = $conf['class']; if ($conf['titleText']) { $this->classesAnchorDefaultTitle[$conf['type']] = $this->contentLanguageService->sL(trim($conf['titleText'])); } if (isset($conf['target'])) { $this->classesAnchorDefaultTarget[$conf['type']] = trim($conf['target']); } } } if ($titleReadOnly && $conf['titleText']) { $this->classesAnchorClassTitle[$conf['class']] = ($this->classesAnchorDefaultTitle[$conf['type']] = $this->contentLanguageService->sL(trim($conf['titleText']))); } } } } // Constructing the class selector options foreach ($classesAnchorArray as $class) { if (!in_array($class, $classesAnchor['all']) || in_array($class, $classesAnchor['all']) && is_array($classesAnchor[$this->displayedLinkHandlerId]) && in_array($class, $classesAnchor[$this->displayedLinkHandlerId])) { $selected = ''; if ($this->linkAttributeValues['class'] === $class || !$this->linkAttributeValues['class'] && $this->classesAnchorDefault[$this->displayedLinkHandlerId] == $class) { $selected = 'selected="selected"'; } $classLabel = !empty($this->RTEProperties['classes.'][$class . '.']['name']) ? $this->getPageConfigLabel($this->RTEProperties['classes.'][$class . '.']['name'], 0) : $class; $classStyle = !empty($this->RTEProperties['classes.'][$class . '.']['value']) ? $this->RTEProperties['classes.'][$class . '.']['value'] : ''; $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] .= '<option ' . $selected . ' value="' . $class . '"' . ($classStyle ? ' style="' . $classStyle . '"' : '') . '>' . $classLabel . '</option>'; } } if ($this->classesAnchorJSOptions[$this->displayedLinkHandlerId] && !($this->buttonConfig['properties.']['class.']['required'] || $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['class.']['required'])) { $selected = ''; if (!$this->linkAttributeValues['class'] && !$this->classesAnchorDefault[$this->displayedLinkHandlerId]) { $selected = 'selected="selected"'; } $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] = '<option ' . $selected . ' value=""></option>' . $this->classesAnchorJSOptions[$this->displayedLinkHandlerId]; } } // Default target $this->defaultLinkTarget = $this->classesAnchorDefault[$this->displayedLinkHandlerId] && $this->classesAnchorDefaultTarget[$this->displayedLinkHandlerId] ? $this->classesAnchorDefaultTarget[$this->displayedLinkHandlerId] : (isset($this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['target.']['default']) ? $this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['target.']['default'] : (isset($this->buttonConfig['properties.']['target.']['default']) ? $this->buttonConfig['properties.']['target.']['default'] : '')); // Initializing additional attributes if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rtehtmlarea']['plugins']['TYPO3Link']['additionalAttributes']) { $addAttributes = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rtehtmlarea']['plugins']['TYPO3Link']['additionalAttributes'], true); foreach ($addAttributes as $attribute) { $this->additionalAttributes[$attribute] = isset($this->linkAttributeValues[$attribute]) ? $this->linkAttributeValues[$attribute] : ''; } } return parent::renderLinkAttributeFields(); } /** * Localize a label obtained from Page TSConfig * * @param string $string The label to be localized * @param bool $JScharCode If needs to be converted to an array of char numbers * @return string Localized string */ public function getPageConfigLabel($string, $JScharCode = true) { if (substr($string, 0, 4) !== 'LLL:') { $label = $string; } else { $label = $this->getLanguageService()->sL(trim($string)); } $label = str_replace('"', '\\"', str_replace('\\\'', '\'', $label)); return $JScharCode ? GeneralUtility::quoteJSvalue($label) : $label; } /** * @return string */ protected function renderCurrentUrl() { $removeLink = '<a href="#" class="btn btn-default t3js-removeCurrentLink">' . $this->getLanguageService()->getLL('removeLink', true) . '</a>'; return ' <table border="0" cellpadding="0" cellspacing="0" id="typo3-curUrl"> <tr> <td>' . $this->getLanguageService()->getLL('currentLink', true) . ': ' . htmlspecialchars($this->currentLinkHandler->formatCurrentUrl()) . $removeLink . '</td> </tr> </table>'; } /** * Get the allowed items or tabs * * @return string[] */ protected function getAllowedItems() { $allowedItems = parent::getAllowedItems(); $blindLinkOptions = isset($this->thisConfig['blindLinkOptions']) ? GeneralUtility::trimExplode(',', $this->thisConfig['blindLinkOptions'], true) : []; $allowedItems = array_diff($allowedItems, $blindLinkOptions); if (is_array($this->buttonConfig['options.']) && $this->buttonConfig['options.']['removeItems']) { $allowedItems = array_diff($allowedItems, GeneralUtility::trimExplode(',', $this->buttonConfig['options.']['removeItems'], true)); } return $allowedItems; } /** * Get the allowed link attributes * * @return string[] */ protected function getAllowedLinkAttributes() { $allowedLinkAttributes = parent::getAllowedLinkAttributes(); $blindLinkFields = isset($this->thisConfig['blindLinkFields']) ? GeneralUtility::trimExplode(',', $this->thisConfig['blindLinkFields'], true) : []; $allowedLinkAttributes = array_diff($allowedLinkAttributes, $blindLinkFields); return $allowedLinkAttributes; } /** * Create an array of link attribute field rendering definitions * * @return string[] */ protected function getLinkAttributeFieldDefinitions() { $fieldRenderingDefinitions = parent::getLinkAttributeFieldDefinitions(); $fieldRenderingDefinitions['title'] = $this->getTitleField(); $fieldRenderingDefinitions['class'] = $this->getClassField(); $fieldRenderingDefinitions['target'] = $this->getTargetField(); $fieldRenderingDefinitions['rel'] = $this->getRelField(); if (empty($this->buttonConfig['queryParametersSelector.']['enabled'])) { unset($fieldRenderingDefinitions['params']); } return $fieldRenderingDefinitions; } /** * Add rel field * * @return string */ protected function getRelField() { if (empty($this->buttonConfig['relAttribute.']['enabled'])) { return ''; } // @todo add rel to attributes $currentRel = $this->displayedLinkHandler === $this->currentLinkHandler && !empty($this->currentLinkParts) ? $this->linkAttributeValues['rel'] : ''; // @todo define label "linkRelationship" below in xlf return ' <form action="" name="lrelform" id="lrelform" class="t3js-dummyform"> <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkRel"> <tr> <td><label>' . $this->getLanguageService()->getLL('linkRelationship', true) . ':</label></td> <td colspan="3"><input type="text" name="lrel" value="' . $currentRel . '" /></td> </tr> </table> </form> '; } /** * Add target selector * * @return string */ protected function getTargetField() { $targetSelectorConfig = array(); if (is_array($this->buttonConfig['targetSelector.'])) { $targetSelectorConfig = $this->buttonConfig['targetSelector.']; } $target = $this->linkAttributeValues['target'] ?: $this->defaultLinkTarget; $lang = $this->getLanguageService(); $targetSelector = ''; if (!$targetSelectorConfig['disabled']) { $targetSelector = ' <select name="ltarget_type" class="t3js-targetPreselect"> <option value=""></option> <option value="_top">' . $lang->getLL('top', true) . '</option> <option value="_blank">' . $lang->getLL('newWindow', true) . '</option> </select> '; } return ' <form action="" name="ltargetform" id="ltargetform" class="t3js-dummyform"> <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTarget"> <tr' . ($targetSelectorConfig['disabled'] ? ' style="display: none;"' : '') . '> <td style="width: 96px;">' . $lang->getLL('target', true) . ':</td> <td> <input type="text" name="ltarget" class="t3js-linkTarget" value="' . htmlspecialchars($target) . '" /> ' . $targetSelector . ' </td> </tr> </table> </form> '; } /** * Add title selector * * @return string */ protected function getTitleField() { if ($this->linkAttributeValues['title']) { $title = $this->linkAttributeValues['title']; } else { $title = !$this->classesAnchorDefault[$this->displayedLinkHandlerId] ? '' : $this->classesAnchorDefaultTitle[$this->displayedLinkHandlerId]; } if (isset($this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly'])) { $readOnly = (bool)$this->buttonConfig[$this->displayedLinkHandlerId . '.']['properties.']['title.']['readOnly']; } else { $readOnly = isset($this->buttonConfig['properties.']['title.']['readOnly']) ? (bool) $this->buttonConfig['properties.']['title.']['readOnly'] : false; } if ($readOnly) { $currentClass = $this->linkAttributeFields['class']; if (!$currentClass) { $currentClass = empty($this->classesAnchorDefault[$this->displayedLinkHandlerId]) ? '' : $this->classesAnchorDefault[$this->displayedLinkHandlerId]; } $title = $currentClass ? $this->classesAnchorClassTitle[$currentClass] : $this->classesAnchorDefaultTitle[$this->displayedLinkHandlerId]; } return ' <form action="" name="ltitleform" id="ltitleform" class="t3js-dummyform"> <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkTitle"> <tr> <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> <td> <span id="rtehtmlarea-browse-links-title-input" style="display: ' . ($readOnly ? 'none' : 'inline') . ';"> <input type="text" id="rtehtmlarea-browse-links-anchor_title" name="ltitle" value="' . htmlspecialchars($title) . '" /> </span> <span id="rtehtmlarea-browse-links-title-readonly" style="display: ' . ($readOnly ? 'inline' : 'none') . ';">' . htmlspecialchars($title) . '</span> </td> </tr> </table> </form> '; } /** * Return html code for the class selector * * @return string the html code to be added to the form */ protected function getClassField() { $selectClass = ''; if ($this->classesAnchorJSOptions[$this->displayedLinkHandlerId]) { $selectClass = ' <form action="" name="lclassform" id="lclassform" class="t3js-dummyform"> <table border="0" cellpadding="2" cellspacing="1" id="typo3-linkClass"> <tr> <td style="width: 96px;">' . $this->getLanguageService()->getLL('anchor_class', true) . '</td> <td><select name="lclass" class="t3js-class-selector"> ' . $this->classesAnchorJSOptions[$this->displayedLinkHandlerId] . ' </select></td> </tr> </table> </form> '; } return $selectClass; } /** * Return the ID of current page * * @return int */ protected function getCurrentPageId() { return explode(':', $this->RTEtsConfigParams)[5]; } /** * Retrieve the configuration * * This is only used by RTE currently. * * @return array */ public function getConfiguration() { return $this->buttonConfig; } /** * Get attributes for the body tag * * @return string[] Array of body-tag attributes */ protected function getBodyTagAttributes() { $parameters = parent::getBodyTagAttributes(); $parameters['data-site-url'] = $this->siteUrl; return $parameters; } /** * @param array $overrides * * @return array Array of parameters which have to be added to URLs */ public function getUrlParameters(array $overrides = null) { return [ 'act' => isset($overrides['act']) ? $overrides['act'] : $this->displayedLinkHandlerId, 'bparams' => $this->bparams, 'editorNo' => $this->editorNo, 'contentTypo3Language' => $this->contentTypo3Language ]; } }