Forked from
TYPO3 / tinymce
35 commits behind the upstream repository.
-
Marco Huber authored
In a flexform (f.e. powermail) the RTE id can have a . which has to be escaped to make the jquery selector work
Marco Huber authoredIn a flexform (f.e. powermail) the RTE id can have a . which has to be escaped to make the jquery selector work
Loader.php 13.52 KiB
<?php
namespace SGalinski\Tinymce;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (http://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Lang\LanguageService;
/**
* tinyMCE initialisation class
*
* Usage: *
* // @var Loader $tinyMceLoader
* $tinyMceLoader = GeneralUtility::makeInstance('SGalinski\Tinymce\Loader');
* $tinyMceLoader->loadConfiguration($configurationFile)
* $tinyMceLoader->loadJsViaPageRenderer($GLOBALS['TSFE']->getPageRenderer());
*
* Basic Configuration: (= content of $configurationFile)
*
* tinymce.init({
* selector: 'textarea'
* });
*/
class Loader {
/**
* TinyMCE configuration
*
* @var array
*/
protected $tinymceConfiguration = [];
/**
* Initialization flag
*
* @var bool
*/
static protected $init = FALSE;
/**
* That's a map, which contains the differences in relation to the tinyMce languages.
*
* @var array
*/
protected $typo3LanguagesDifferencesToTinyMceLanguagesMap = [
'bg' => 'bg_BG',
'fr' => 'fr_FR',
'fr_CA' => 'fr_FR',
'kl' => 'gl',
'he' => 'he_IL',
'hi' => 'hi_IN',
'hu' => 'hu_HU',
'is' => 'is_IS',
'km' => 'km_KH',
'no' => 'nb_NO',
'pt' => 'pt_PT',
'sl' => 'sl_SI',
'sv' => 'sv_SE',
'th' => 'th_TH',
'zh' => 'zh_TW',
];
/**
* @param string $configuration file reference or configuration string (defaults to basic configuration)
* @param boolean $forceLanguage set this to true if you want to force your language set by the configuration
* @return void
*/
public function loadConfiguration($configuration = '', $forceLanguage = FALSE) {
self::$init = FALSE;
$this->tinymceConfiguration = $this->prepareTinyMCEConfiguration($configuration);
if (!$forceLanguage) {
$this->setLanguage();
}
}
/**
* Calculates and sets the current language
*
* @return void
*/
protected function setLanguage() {
/** @var $languageInstance LanguageService */
$languageInstance = (TYPO3_MODE === 'FE' ? $GLOBALS['TSFE'] : $GLOBALS['LANG']);
$languageKey = $languageInstance->lang;
if (TYPO3_MODE === 'BE') {
$groupOrUserProps = BackendUtility::getModTSconfig('', 'tx_tinyMCE');
if (trim($groupOrUserProps['properties']['prefLang']) !== '') {
$languageKey = $groupOrUserProps['properties']['prefLang'];
}
}
// language conversion from TLD to iso631
/** @var $locales \TYPO3\CMS\Core\Localization\Locales */
$locales = GeneralUtility::makeInstance('TYPO3\CMS\Core\Localization\Locales');
$locales->initialize();
$isoArray = (array) $locales->getIsoMapping();
if (array_key_exists($languageKey, $isoArray)) {
$languageKey = $isoArray[$languageKey];
}
if (array_key_exists($languageKey, $this->typo3LanguagesDifferencesToTinyMceLanguagesMap)) {
$languageKey = $this->typo3LanguagesDifferencesToTinyMceLanguagesMap[$languageKey];
}
$languageFile = PATH_site . ExtensionManagementUtility::siteRelPath('tinymce') .
'tinymce_node_modules/tinymce/langs/' . $languageKey . '.js';
if (!is_file($languageFile)) {
$languageKey = 'en';
}
$this->addConfigurationOption('language', $languageKey);
}
/**
* Returns a file that contains the tinyMCE configuration
*
* Note: The load dom event cannot be used, because e.g. IRRE adds the javascript
* later on. This leads to code that is never executed. The interval timer hack fixes this
* issue.
*
* @return string
*/
protected function getConfiguration() {
$configuration = $this->tinymceConfiguration['preJS'];
$configuration .= '
var SG = SG || {};
SG.domIsReady = (function(domIsReady) {
var isBrowserIeOrNot = function() {
return (!document.attachEvent || typeof document.attachEvent === "undefined" ? "not-ie" : "ie");
}
domIsReady = function(callback) {
if(callback && typeof callback === "function"){
if(isBrowserIeOrNot() !== "ie") {
document.addEventListener("DOMContentLoaded", function() {
return callback();
});
} else {
document.attachEvent("onreadystatechange", function() {
if(document.readyState === "complete") {
return callback();
}
});
}
} else {
console.error("The callback is not a function!");
}
}
return domIsReady;
})(SG.domIsReady || {});
SG.initTinyMceLoadFunction = function() {
if (SG.initializedTinyMceLoaderInstance) {
if (SG.initTinyMceLoadInterval) {
clearInterval(SG.initTinyMceLoadInterval);
}
return;
}
if (SG.TinyMceLoader && window.tinymce && window.tinymce.init) {
SG.initializedTinyMceLoaderInstance = new SG.TinyMceLoader(window.tinymce, {
' . $this->replaceTypo3Paths($this->tinymceConfiguration['configurationData']) . '
});
if (SG.initTinyMceLoadInterval) {
clearInterval(SG.initTinyMceLoadInterval);
}
}
};
SG.domIsReady(function() {
SG.initTinyMceLoadFunction();
});
// the content ready event is not thrown if RTE fields are loaded via IRRE
// so we need to check at least after some time if the function was really called
SG.initTinyMceLoadInterval = window.setInterval(SG.initTinyMceLoadFunction, 1500);
';
$configuration .= $this->tinymceConfiguration['postJS'];
$filename = 'tinymceConfiguration' . sha1($configuration) . '.js';
$file = PATH_site . 'typo3temp/' . $filename;
if (!is_file($file)) {
file_put_contents($file, $configuration);
GeneralUtility::fixPermissions($file);
}
return $this->getPath($file, TRUE);
}
/**
* Returns the needed javascript inclusion code
*
* Note: This function can only be called once.
*
* @return string
*/
public function getJS() {
$output = '';
if (!self::$init) {
self::$init = TRUE;
$pathToTinyMceExtension = ExtensionManagementUtility::extRelPath('tinymce');
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'tinymce_node_modules/tinymce/tinymce.min.js';
$output = '<script type="text/javascript" src="' . $script . '"></script>';
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Contrib/WeakMap/WeakMap.js';
$output .= '<script type="text/javascript" src="' . $script . '"></script>';
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Contrib/MutationObserver/MutationObserver.js';
$output .= '<script type="text/javascript" src="' . $script . '"></script>';
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Resources/Public/JavaScript/Loader.js';
$output .= '<script type="text/javascript" src="' . $script . '"></script>';
$script = $this->getConfiguration();
$output .= '<script type="text/javascript" src="' . $script . '"></script>';
}
return $output;
}
/**
* Loads the required javascript via the given page renderer instance
*
* Note: This function can only be called once.
*
* @param PageRenderer $pageRenderer
* @return void
*/
public function loadJsViaPageRenderer(PageRenderer $pageRenderer) {
if (self::$init) {
return;
}
self::$init = TRUE;
$pathToTinyMceExtension = ExtensionManagementUtility::extRelPath('tinymce');
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'tinymce_node_modules/tinymce/tinymce.min.js';
$pageRenderer->addJsLibrary('tinymce', $script, 'text/javascript', FALSE, TRUE, '', TRUE);
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Contrib/MutationObserver/MutationObserver.js';
$pageRenderer->addJsLibrary('MutationObserver', $script, 'text/javascript', FALSE, TRUE, '', TRUE);
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Contrib/WeakMap/WeakMap.js';
$pageRenderer->addJsLibrary('WeakMap', $script, 'text/javascript', FALSE, TRUE, '', TRUE);
$script = $GLOBALS['BACK_PATH'] . $pathToTinyMceExtension . 'Resources/Public/JavaScript/Loader.js';
$pageRenderer->addJsFile($script, 'text/javascript', FALSE, TRUE, '', TRUE);
$script = $this->getConfiguration();
$pageRenderer->addJsFile($script, 'text/javascript', FALSE, TRUE, '', TRUE);
}
/**
* Loads the required javascript via the require.js
*
* @return array
* @see \SGalinski\Tinymce4Rte\Form\Element\RichTextElement->loadRequireModulesForRTE
*/
public function loadJsViaRequireJS() {
if (self::$init) {
return [];
}
self::$init = TRUE;
$pathToTinyMceExtension = ExtensionManagementUtility::extPath('tinymce');
$tinymceSource = $pathToTinyMceExtension . 'tinymce_node_modules/tinymce/tinymce.min.js';
$configuration = $this->tinymceConfiguration['preJS'];
$configuration .= '
var $ = jQuery = window.TYPO3.jQuery;
var RTEarea = RTEarea || window.RTEarea;
define([\'TYPO3/CMS/Tinymce/../../../../typo3conf/ext/tinymce/tinymce_node_modules/tinymce/jquery.tinymce.min.js\'], function () {
$(\'.tinymce4_rte#RTEarea' . str_replace('.', '\\\\.', $this->tinymceConfiguration['configurationDataArray']['editornumber']) . '\').tinymce({
script_url : \'' . $this->getPath($tinymceSource, TRUE) . '\',
' . $this->replaceTypo3Paths($this->tinymceConfiguration['configurationData']) . ',
selector: \'.tinymce4_rte#RTEarea' . str_replace('.', '\\\\.', $this->tinymceConfiguration['configurationDataArray']['editornumber']) . '\'
});
});
';
$configuration .= $this->tinymceConfiguration['postJS'];
$filename = 'tinymceConfiguration' . sha1($configuration) . '.js';
$file = PATH_site . 'typo3temp/' . $filename;
if (!is_file($file)) {
file_put_contents($file, $configuration);
GeneralUtility::fixPermissions($file);
}
return [$this->getPath($file, TRUE)];
}
/**
* Parses and processes the tinyMCE configuration
*
* @param string $configuration file reference or configuration string
* @return array
*/
protected function prepareTinyMCEConfiguration($configuration) {
$configurationArray = [];
// try to resolve a potential TYPO3 file path
$configurationFile = GeneralUtility::getFileAbsFileName($configuration);
if (is_file($configurationFile)) {
$configuration = file_get_contents($configurationFile);
}
// first try to find the configuration via the "subpart" ###TINYMCE_INIT###
$pattern = '/(.*)?tinymce\.init\s*\(\s*\{\s*\/\*\s?###TINYMCE_INIT###.*?\*\/(.*)\/\*\s*###TINYMCE_INIT###.*?\*\/\s*\}\s*\);*(.*)?/is';
if (@preg_match($pattern, $configuration, $matches)) {
// fine :)
} else {
// if nothing is found, try it the legacy way (note: this may cause problems with a complex setups, since parenthesis-matching is not perfect here)
$pattern = '/(.*)tinymce\.init\s*\(\s*\{(.*?)\}\s*\)\s*;?(.*)/is';
preg_match($pattern, $configuration, $matches);
}
// add preJS and postJS
$configurationArray['preJS'] = trim($matches[1]);
$configurationArray['configurationData'] = trim($matches[2]);
$configurationArray['postJS'] = trim($matches[3]);
return $configurationArray;
}
/**
* Adds a basic configuration value to the parsed configuration
*
* @param string $key
* @param mixed $value
*/
public function addConfigurationOption($key, $value) {
if (is_numeric($value)) {
if (strpos($value, '.')) {
$value = (float) $value;
} else {
$value = (int) $value;
}
} elseif (strpos(trim($value), '[') === FALSE && strpos(trim($value), '{') === FALSE &&
strpos(trim($value), 'function') === FALSE
) {
$value = '\'' . $value . '\'';
}
if ($this->tinymceConfiguration['configurationData'] !== '') {
$this->tinymceConfiguration['configurationData'] .= "\n,";
}
$this->tinymceConfiguration['configurationData'] .= $key . ': ' . $value;
$this->tinymceConfiguration['configurationDataArray'][$key] = $value;
}
/**
* Replaces any TYPO3 extension path with the domain prefixed one.
*
* @param string $configuration
* @return string
*/
protected function replaceTypo3Paths($configuration) {
$replacementFunction = function ($value) {
// getPath should be used, but this causes a php exception with PHP 5.3 as $this isn't set there
return '\'' . GeneralUtility::getIndpEnv('TYPO3_SITE_URL') .
str_replace(PATH_site, '', GeneralUtility::getFileAbsFileName($value[1])) . '\'';
};
return preg_replace_callback('/["\'](EXT:[^"\']*)["\']/is', $replacementFunction, $configuration);
}
/**
* Resolves a relative path like EXT:tinymce/... into an absolute one that contains either the
* current host or the path to the file in the file system.
*
* @param string $relativePath
* @param bool $returnWithDomain
* @return string
*/
protected function getPath($relativePath, $returnWithDomain = FALSE) {
$finalPath = $absolutePath = GeneralUtility::getFileAbsFileName($relativePath);
if ($returnWithDomain) {
$finalPath = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . str_replace(PATH_site, '', $absolutePath);
}
return $finalPath;
}
}
?>