[BUGFIX] Quickedit mode: RTE has no user CSS
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Classes / RteHtmlAreaBase.php
1 <?php
2 namespace TYPO3\CMS\Rtehtmlarea;
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 TYPO3\CMS\Backend\Form\FormEngine;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * A RTE using the htmlArea editor
23 *
24 * @author Philipp Borgmann <philipp.borgmann@gmx.de>
25 * @author Stanislas Rolland <typo3(arobas)sjbr.ca>
26 */
27 class RteHtmlAreaBase extends \TYPO3\CMS\Backend\Rte\AbstractRte {
28
29 // Configuration of supported browsers
30 /**
31 * @var array
32 */
33 public $conf_supported_browser = array(
34 'msie' => array(
35 array(
36 'version' => 6.0,
37 'system' => array(
38 'allowed' => array(
39 'winNT',
40 'win98',
41 'win95'
42 )
43 )
44 )
45 ),
46 'gecko' => array(
47 array(
48 'version' => 1.8
49 )
50 ),
51 'webkit' => array(
52 array(
53 'version' => 534
54 ),
55 array(
56 'version' => 523,
57 'system' => array(
58 'disallowed' => array(
59 'iOS',
60 'android'
61 )
62 )
63 )
64 ),
65 'opera' => array(
66 array(
67 'version' => 9.62,
68 'system' => array(
69 'disallowed' => array(
70 'iOS',
71 'android'
72 )
73 )
74 )
75 )
76 );
77
78 // Always hide these toolbar buttons (TYPO3 button name)
79 /**
80 * @var array
81 */
82 public $conf_toolbar_hide = array(
83 'showhelp'
84 );
85
86 // The order of the toolbar: the name is the TYPO3-button name
87 /**
88 * @var string
89 */
90 public $defaultToolbarOrder;
91
92 // Conversion array: TYPO3 button names to htmlArea button names
93 /**
94 * @var array
95 */
96 public $convertToolbarForHtmlAreaArray = array(
97 'showhelp' => 'ShowHelp',
98 'space' => 'space',
99 'bar' => 'separator',
100 'linebreak' => 'linebreak'
101 );
102
103 /**
104 * @var array
105 */
106 public $pluginButton = array();
107
108 /**
109 * @var array
110 */
111 public $pluginLabel = array();
112
113 // Alternative style for RTE <div> tag.
114 public $RTEdivStyle;
115
116 // Relative path to this extension. It ends with "/"
117 public $extHttpPath;
118
119 public $backPath = '';
120
121 // TYPO3 site url
122 public $siteURL;
123
124 // TYPO3 host url
125 public $hostURL;
126
127 // Typo3 version
128 public $typoVersion;
129
130 // Identifies the RTE as being the one from the "rtehtmlarea" extension if any external code needs to know
131 /**
132 * @var string
133 */
134 public $ID = 'rtehtmlarea';
135
136 // If set, the content goes into a regular TEXT area field - for developing testing of transformations.
137 /**
138 * @var bool
139 */
140 public $debugMode = FALSE;
141
142 // For the editor
143 /**
144 * @var array
145 */
146 public $client;
147
148 /**
149 * Reference to parent object, which is an instance of the TCEforms
150 *
151 * @var \TYPO3\CMS\Backend\Form\FormEngine
152 */
153 public $TCEform;
154
155 /**
156 * @var string
157 */
158 public $elementId;
159
160 /**
161 * @var array
162 */
163 public $elementParts;
164
165 /**
166 * @var string
167 */
168 public $tscPID;
169
170 /**
171 * @var string
172 */
173 public $typeVal;
174
175 /**
176 * @var int
177 */
178 public $thePid;
179
180 /**
181 * @var array
182 */
183 public $RTEsetup;
184
185 /**
186 * @var array
187 */
188 public $thisConfig;
189
190 public $language;
191 /**
192 * TYPO3 language code of the content language
193 */
194 public $contentTypo3Language;
195 /**
196 * ISO language code of the content language
197 */
198 public $contentISOLanguage;
199 /**
200 * Language service object for localization to the content language
201 */
202 protected $contentLanguageService;
203 public $charset = 'utf-8';
204
205 public $contentCharset = 'utf-8';
206
207 public $OutputCharset = 'utf-8';
208
209 /**
210 * @var string
211 */
212 public $editorCSS;
213
214 /**
215 * @var array
216 */
217 public $specConf;
218
219 /**
220 * @var array
221 */
222 public $toolbar = array();
223
224 // Save the buttons for the toolbar
225 /**
226 * @var array
227 */
228 public $toolbarOrderArray = array();
229
230 protected $pluginEnabledArray = array();
231
232 // Array of plugin id's enabled in the current RTE editing area
233 protected $pluginEnabledCumulativeArray = array();
234
235 // Cumulative array of plugin id's enabled so far in any of the RTE editing areas of the form
236 public $registeredPlugins = array();
237
238 // Array of registered plugins indexed by their plugin Id's
239 protected $fullScreen = FALSE;
240 // Page renderer object
241 protected $pageRenderer;
242
243 /**
244 * Returns TRUE if the RTE is available. Here you check if the browser requirements are met.
245 * If there are reasons why the RTE cannot be displayed you simply enter them as text in ->errorLog
246 *
247 * @return bool TRUE if this RTE object offers an RTE in the current browser environment
248 */
249 public function isAvailable() {
250 $this->client = $this->clientInfo();
251 $this->errorLog = array();
252 if (!$this->debugMode) {
253 // If debug-mode, let any browser through
254 $rteIsAvailable = FALSE;
255 $rteConfBrowser = $this->conf_supported_browser;
256 if (is_array($rteConfBrowser)) {
257 foreach ($rteConfBrowser as $browser => $browserConf) {
258 if ($browser == $this->client['browser']) {
259 // Config for Browser found, check it:
260 if (is_array($browserConf)) {
261 foreach ($browserConf as $browserConfSub) {
262 if ($browserConfSub['version'] <= $this->client['version'] || empty($browserConfSub['version'])) {
263 // Version is supported
264 if (is_array($browserConfSub['system'])) {
265 // Check against allowed systems
266 if (is_array($browserConfSub['system']['allowed'])) {
267 foreach ($browserConfSub['system']['allowed'] as $system) {
268 if (in_array($system, $this->client['all_systems'])) {
269 $rteIsAvailable = TRUE;
270 break;
271 }
272 }
273 } else {
274 // All allowed
275 $rteIsAvailable = TRUE;
276 }
277 // Check against disallowed systems
278 if (is_array($browserConfSub['system']['disallowed'])) {
279 foreach ($browserConfSub['system']['disallowed'] as $system) {
280 if (in_array($system, $this->client['all_systems'])) {
281 $rteIsAvailable = FALSE;
282 break;
283 }
284 }
285 }
286 } else {
287 // No system config: system is supported
288 $rteIsAvailable = TRUE;
289 break;
290 }
291 }
292 }
293 } else {
294 // no config for this browser found, so all versions or system with this browsers are allow
295 $rteIsAvailable = TRUE;
296 break;
297 }
298 }
299 }
300 } else {
301
302 }
303 if (!$rteIsAvailable) {
304 $this->errorLog[] = 'RTE: Browser not supported.';
305 }
306 if (\TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version) < 4000000) {
307 $rteIsAvailable = FALSE;
308 $this->errorLog[] = 'rte: This version of htmlArea RTE cannot run under this version of TYPO3.';
309 }
310 }
311 return $rteIsAvailable;
312 }
313
314 /**
315 * Draws the RTE as an iframe
316 *
317 * @param FormEngine $parentObject Reference to parent object, which is an instance of the TCEforms.
318 * @param string $table The table name
319 * @param string $field The field name
320 * @param array $row The current row from which field is being rendered
321 * @param array $PA Array of standard content for rendering form fields from TCEforms. See TCEforms for details on this. Includes for instance the value and the form field name, java script actions and more.
322 * @param array $specConf "special" configuration - what is found at position 4 in the types configuration of a field from record, parsed into an array.
323 * @param array $thisConfig Configuration for RTEs; A mix between TSconfig and otherwise. Contains configuration for display, which buttons are enabled, additional transformation information etc.
324 * @param string $RTEtypeVal Record "type" field value.
325 * @param string $RTErelPath Relative path for images/links in RTE; this is used when the RTE edits content from static files where the path of such media has to be transformed forth and back!
326 * @param int $thePidValue PID value of record (true parent page id)
327 * @return string HTML code for RTE!
328 */
329 public function drawRTE($parentObject, $table, $field, $row, $PA, $specConf, $thisConfig, $RTEtypeVal, $RTErelPath, $thePidValue) {
330 global $LANG, $TYPO3_DB;
331 $this->TCEform = $parentObject;
332 $inline = $this->TCEform->inline;
333 $LANG->includeLLFile('EXT:' . $this->ID . '/locallang.xml');
334 $this->client = $this->clientInfo();
335 $this->typoVersion = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version);
336 $this->userUid = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
337 // Draw form element:
338 if ($this->debugMode) {
339 // Draws regular text area (debug mode)
340 $item = parent::drawRTE($this->TCEform, $table, $field, $row, $PA, $specConf, $thisConfig, $RTEtypeVal, $RTErelPath, $thePidValue);
341 } else {
342 // Draw real RTE
343 /* =======================================
344 * INIT THE EDITOR-SETTINGS
345 * =======================================
346 */
347 // Set backPath
348 $this->backPath = $this->TCEform->backPath;
349 // Get the path to this extension:
350 $this->extHttpPath = $this->backPath . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($this->ID);
351 // Get the site URL
352 $this->siteURL = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
353 // Get the host URL
354 $this->hostURL = $this->siteURL . TYPO3_mainDir;
355 // Element ID + pid
356 $this->elementId = $PA['itemFormElName'];
357 // Form element name
358 $this->elementParts = explode('][', preg_replace('/\\]$/', '', preg_replace('/^(TSFE_EDIT\\[data\\]\\[|data\\[)/', '', $this->elementId)));
359 // Find the page PIDs:
360 list($this->tscPID, $this->thePid) = BackendUtility::getTSCpid(trim($this->elementParts[0]), trim($this->elementParts[1]), $thePidValue);
361 // Record "types" field value:
362 $this->typeVal = $RTEtypeVal;
363 // TCA "types" value for record
364 // Find "thisConfig" for record/editor:
365 unset($this->RTEsetup);
366 $this->RTEsetup = $GLOBALS['BE_USER']->getTSConfig('RTE', BackendUtility::getPagesTSconfig($this->tscPID));
367 $this->thisConfig = $thisConfig;
368 // Special configuration and default extras:
369 $this->specConf = $specConf;
370 if ($this->thisConfig['forceHTTPS']) {
371 $this->extHttpPath = preg_replace('/^(http|https)/', 'https', $this->extHttpPath);
372 $this->siteURL = preg_replace('/^(http|https)/', 'https', $this->siteURL);
373 $this->hostURL = preg_replace('/^(http|https)/', 'https', $this->hostURL);
374 }
375 // Register RTE windows
376 $this->TCEform->RTEwindows[] = $PA['itemFormElName'];
377 $textAreaId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $PA['itemFormElName']);
378 $textAreaId = htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $textAreaId));
379 /* =======================================
380 * LANGUAGES & CHARACTER SETS
381 * =======================================
382 */
383 // Languages: interface and content
384 $this->language = $GLOBALS['LANG']->lang;
385 if ($this->language === 'default' || !$this->language) {
386 $this->language = 'en';
387 }
388 $this->contentLanguageUid = max($row['sys_language_uid'], 0);
389 if ($this->contentLanguageUid) {
390 $this->contentISOLanguage = $this->language;
391 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('static_info_tables')) {
392 $tableA = 'sys_language';
393 $tableB = 'static_languages';
394 $selectFields = $tableA . '.uid,' . $tableB . '.lg_iso_2,' . $tableB . '.lg_country_iso_2';
395 $tableAB = $tableA . ' LEFT JOIN ' . $tableB . ' ON ' . $tableA . '.static_lang_isocode=' . $tableB . '.uid';
396 $whereClause = $tableA . '.uid = ' . intval($this->contentLanguageUid);
397 $whereClause .= BackendUtility::BEenableFields($tableA);
398 $whereClause .= BackendUtility::deleteClause($tableA);
399 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($selectFields, $tableAB, $whereClause);
400 while ($languageRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
401 $this->contentISOLanguage = strtolower(trim($languageRow['lg_iso_2']) . (trim($languageRow['lg_country_iso_2']) ? '_' . trim($languageRow['lg_country_iso_2']) : ''));
402 }
403 }
404 } else {
405 $this->contentISOLanguage = trim($this->thisConfig['defaultContentLanguage']) ?: 'en';
406 $languageCodeParts = explode('_', $this->contentISOLanguage);
407 $this->contentISOLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
408 // Find the configured language in the list of localization locales
409 /** @var $locales \TYPO3\CMS\Core\Localization\Locales */
410 $locales = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Localization\\Locales');
411 // If not found, default to 'en'
412 if (!in_array($this->contentISOLanguage, $locales->getLocales())) {
413 $this->contentISOLanguage = 'en';
414 }
415 }
416 // Create content laguage service
417 $this->contentLanguageService = GeneralUtility::makeInstance('TYPO3\\CMS\\Lang\\LanguageService');
418 $this->contentTypo3Language = $this->contentISOLanguage === 'en' ? 'default' : $this->contentISOLanguage;
419 $this->contentLanguageService->init($this->contentTypo3Language);
420 /* =======================================
421 * TOOLBAR CONFIGURATION
422 * =======================================
423 */
424 $this->initializeToolbarConfiguration();
425 /* =======================================
426 * SET STYLES
427 * =======================================
428 */
429 // Check if wizard_rte called this for fullscreen edtition
430 if (GeneralUtility::_GP('M') === 'wizard_rte') {
431 $this->fullScreen = TRUE;
432 $RTEWidth = '100%';
433 $RTEHeight = '100%';
434 $RTEPaddingRight = '0';
435 $editorWrapWidth = '100%';
436 } else {
437 $options = $GLOBALS['BE_USER']->userTS['options.'];
438 $RTEWidth = 530 + (isset($options['RTELargeWidthIncrement']) ? (int)$options['RTELargeWidthIncrement'] : 150);
439 $RTEWidth -= $inline->getStructureDepth() > 0 ? ($inline->getStructureDepth() + 1) * $inline->getLevelMargin() : 0;
440 $RTEWidthOverride = is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteWidth']) && trim($GLOBALS['BE_USER']->uc['rteWidth']) ? trim($GLOBALS['BE_USER']->uc['rteWidth']) : trim($this->thisConfig['RTEWidthOverride']);
441 if ($RTEWidthOverride) {
442 if (strstr($RTEWidthOverride, '%')) {
443 if ($this->client['browser'] != 'msie') {
444 $RTEWidth = (int)$RTEWidthOverride > 0 ? $RTEWidthOverride : '100%';
445 }
446 } else {
447 $RTEWidth = (int)$RTEWidthOverride > 0 ? (int)$RTEWidthOverride : $RTEWidth;
448 }
449 }
450 $RTEWidth = strstr($RTEWidth, '%') ? $RTEWidth : $RTEWidth . 'px';
451 $RTEHeight = 380 + (isset($options['RTELargeHeightIncrement']) ? (int)$options['RTELargeHeightIncrement'] : 0);
452 $RTEHeightOverride = is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteHeight']) && (int)$GLOBALS['BE_USER']->uc['rteHeight'] ? (int)$GLOBALS['BE_USER']->uc['rteHeight'] : (int)$this->thisConfig['RTEHeightOverride'];
453 $RTEHeight = $RTEHeightOverride > 0 ? $RTEHeightOverride : $RTEHeight;
454 $RTEPaddingRight = '2px';
455 $editorWrapWidth = '99%';
456 }
457 $editorWrapHeight = '100%';
458 $this->RTEdivStyle = 'position:relative; left:0px; top:0px; height:' . $RTEHeight . 'px; width:' . $RTEWidth . '; border: 1px solid black; padding: 2px ' . $RTEPaddingRight . ' 2px 2px;';
459 /* =======================================
460 * LOAD CSS AND JAVASCRIPT
461 * =======================================
462 */
463 $this->pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
464 // Preloading the pageStyle and including RTE skin stylesheets
465 $this->addPageStyle();
466 $this->addSkin();
467 // Register RTE in JS
468 $this->TCEform->additionalJS_post[] = $this->registerRTEinJS($this->TCEform->RTEcounter, $table, $row['uid'], $field, $textAreaId);
469 // Set the save option for the RTE
470 $this->TCEform->additionalJS_submit[] = $this->setSaveRTE($this->TCEform->RTEcounter, $this->TCEform->formName, $textAreaId, $PA['itemFormElName']);
471 $this->TCEform->additionalJS_delete[] = $this->setDeleteRTE($this->TCEform->RTEcounter, $this->TCEform->formName, $textAreaId);
472 // Loading ExtJs inline code
473 $this->pageRenderer->enableExtJSQuickTips();
474 // Add TYPO3 notifications JavaScript
475 $this->pageRenderer->addJsFile('sysext/backend/Resources/Public/JavaScript/notifications.js');
476 // Add RTE JavaScript
477 $this->addRteJsFiles($this->TCEform->RTEcounter);
478 $this->pageRenderer->addJsFile($this->buildJSMainLangFile($this->TCEform->RTEcounter));
479 $this->pageRenderer->addJsInlineCode('HTMLArea-init', $this->getRteInitJsCode(), TRUE);
480 /* =======================================
481 * DRAW THE EDITOR
482 * =======================================
483 */
484 // Transform value:
485 $value = $this->transformContent('rte', $PA['itemFormElValue'], $table, $field, $row, $specConf, $thisConfig, $RTErelPath, $thePidValue);
486 // Further content transformation by registered plugins
487 foreach ($this->registeredPlugins as $pluginId => $plugin) {
488 if ($this->isPluginEnabled($pluginId) && method_exists($plugin, 'transformContent')) {
489 $value = $plugin->transformContent($value);
490 }
491 }
492 // Draw the textarea
493 $visibility = 'hidden';
494 $item = $this->triggerField($PA['itemFormElName']) . '
495 <div id="pleasewait' . $textAreaId . '" class="pleasewait" style="display: block;" >' . $LANG->getLL('Please wait') . '</div>
496 <div id="editorWrap' . $textAreaId . '" class="editorWrap" style="visibility: hidden; width:' . $editorWrapWidth . '; height:' . $editorWrapHeight . ';">
497 <textarea id="RTEarea' . $textAreaId . '" name="' . htmlspecialchars($PA['itemFormElName']) . '" rows="0" cols="0" style="' . htmlspecialchars($this->RTEdivStyle, ENT_COMPAT, 'UTF-8', FALSE) . '">' . GeneralUtility::formatForTextarea($value) . '</textarea>
498 </div>' . LF;
499 }
500 // Return form item:
501 return $item;
502 }
503
504 /**
505 * Add links to content style sheets to document header
506 *
507 * @return void
508 */
509 protected function addPageStyle() {
510 $contentCssFileNames = $this->getContentCssFileNames();
511 foreach ($contentCssFileNames as $contentCssKey => $contentCssFile) {
512 $this->addStyleSheet('rtehtmlarea-content-' . $contentCssKey, $contentCssFile, 'htmlArea RTE Content CSS', 'alternate stylesheet');
513 }
514 }
515
516 /**
517 * Get the name of the contentCSS files to use
518 *
519 * @return array An array of full file name of the content css files to use
520 */
521 protected function getContentCssFileNames() {
522 $contentCss = is_array($this->thisConfig['contentCSS.']) ? $this->thisConfig['contentCSS.'] : array();
523 if (isset($this->thisConfig['contentCSS'])) {
524 $contentCss[] = trim($this->thisConfig['contentCSS']);
525 }
526 $contentCssFiles = array();
527 if (count($contentCss)) {
528 foreach ($contentCss as $contentCssKey => $contentCssfile) {
529 $fileName = trim($contentCssfile);
530 $absolutePath = GeneralUtility::getFileAbsFileName($fileName);
531 if (file_exists($absolutePath) && filesize($absolutePath)) {
532 $contentCssFiles[$contentCssKey] = $this->getFullFileName($fileName);
533 }
534 }
535 }
536 // Fallback to default content css file if none of the configured files exists and is not empty
537 if (count($contentCssFiles) === 0) {
538 $contentCssFiles['default'] = $this->getFullFileName('EXT:' . $this->ID . '/res/contentcss/default.css');
539 }
540 return array_unique($contentCssFiles);
541 }
542
543 /**
544 * Add links to skin style sheet(s) to document header
545 *
546 * @return void
547 */
548 protected function addSkin() {
549 // Get skin file name from Page TSConfig if any
550 $skinFilename = trim($this->thisConfig['skin']) ?: 'EXT:' . $this->ID . '/htmlarea/skins/default/htmlarea.css';
551 $this->editorCSS = $this->getFullFileName($skinFilename);
552 $skinDir = dirname($this->editorCSS);
553 // Editing area style sheet
554 $this->editedContentCSS = $skinDir . '/htmlarea-edited-content.css';
555 $this->addStyleSheet('rtehtmlarea-editing-area-skin', $this->editedContentCSS);
556 // Main skin
557 $this->addStyleSheet('rtehtmlarea-skin', $this->editorCSS);
558 // Additional icons from registered plugins
559 foreach ($this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter] as $pluginId) {
560 if (is_object($this->registeredPlugins[$pluginId])) {
561 $pathToSkin = $this->registeredPlugins[$pluginId]->getPathToSkin();
562 if ($pathToSkin) {
563 $key = $this->registeredPlugins[$pluginId]->getExtensionKey();
564 $this->addStyleSheet('rtehtmlarea-plugin-' . $pluginId . '-skin', ($this->is_FE() ? \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($key) : $this->backPath . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($key)) . $pathToSkin);
565 }
566 }
567 }
568 }
569
570 /**
571 * Add style sheet file to document header
572 *
573 * @param string $key: some key identifying the style sheet
574 * @param string $href: uri to the style sheet file
575 * @param string $title: value for the title attribute of the link element
576 * @param string $relation: value for the rel attribute of the link element
577 * @return void
578 */
579 protected function addStyleSheet($key, $href, $title = '', $relation = 'stylesheet') {
580 // If it was not known that an RTE-enabled CE would be created when the page was first created, the css would not be added to head
581 if (is_object($this->TCEform->inline) && $this->TCEform->inline->isAjaxCall) {
582 $this->TCEform->additionalCode_pre[$key] = '<link rel="' . $relation . '" type="text/css" href="' . $href . '" title="' . $title . '" />';
583 } else {
584 $this->pageRenderer->addCssFile($href, $relation, 'screen', $title);
585 }
586 }
587
588 /**
589 * Initialize toolbar configuration and enable registered plugins
590 *
591 * @return void
592 */
593 protected function initializeToolbarConfiguration() {
594 // Enable registred plugins
595 $this->enableRegisteredPlugins();
596 // Configure toolbar
597 $this->setToolbar();
598 // Check if some plugins need to be disabled
599 $this->setPlugins();
600 // Merge the list of enabled plugins with the lists from the previous RTE editing areas on the same form
601 $this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter] = $this->pluginEnabledArray;
602 if ($this->TCEform->RTEcounter > 1 && isset($this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter - 1]) && is_array($this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter - 1])) {
603 $this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter] = array_unique(array_values(array_merge($this->pluginEnabledArray, $this->pluginEnabledCumulativeArray[$this->TCEform->RTEcounter - 1])));
604 }
605 }
606
607 /**
608 * Add registered plugins to the array of enabled plugins
609 */
610 public function enableRegisteredPlugins() {
611 global $TYPO3_CONF_VARS;
612 // Traverse registered plugins
613 if (is_array($TYPO3_CONF_VARS['EXTCONF'][$this->ID]['plugins'])) {
614 foreach ($TYPO3_CONF_VARS['EXTCONF'][$this->ID]['plugins'] as $pluginId => $pluginObjectConfiguration) {
615 $plugin = FALSE;
616 if (is_array($pluginObjectConfiguration) && count($pluginObjectConfiguration)) {
617 $plugin = GeneralUtility::getUserObj($pluginObjectConfiguration['objectReference']);
618 }
619 if (is_object($plugin)) {
620 if ($plugin->main($this)) {
621 $this->registeredPlugins[$pluginId] = $plugin;
622 // Override buttons from previously registered plugins
623 $pluginButtons = GeneralUtility::trimExplode(',', $plugin->getPluginButtons(), TRUE);
624 foreach ($this->pluginButton as $previousPluginId => $buttonList) {
625 $this->pluginButton[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginButton[$previousPluginId], TRUE), $pluginButtons));
626 }
627 $this->pluginButton[$pluginId] = $plugin->getPluginButtons();
628 $pluginLabels = GeneralUtility::trimExplode(',', $plugin->getPluginLabels(), TRUE);
629 foreach ($this->pluginLabel as $previousPluginId => $labelList) {
630 $this->pluginLabel[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginLabel[$previousPluginId], TRUE), $pluginLabels));
631 }
632 $this->pluginLabel[$pluginId] = $plugin->getPluginLabels();
633 $this->pluginEnabledArray[] = $pluginId;
634 }
635 }
636 }
637 }
638 // Process overrides
639 $hidePlugins = array();
640 foreach ($this->registeredPlugins as $pluginId => $plugin) {
641 if ($plugin->addsButtons() && !$this->pluginButton[$pluginId]) {
642 $hidePlugins[] = $pluginId;
643 }
644 }
645 $this->pluginEnabledArray = array_unique(array_diff($this->pluginEnabledArray, $hidePlugins));
646 }
647
648 /**
649 * Set the toolbar config (only in this PHP-Object, not in JS):
650 */
651 public function setToolbar() {
652 if ($this->client['browser'] == 'msie' || $this->client['browser'] == 'opera') {
653 $this->thisConfig['keepButtonGroupTogether'] = 0;
654 }
655 $this->defaultToolbarOrder = 'bar, blockstylelabel, blockstyle, space, textstylelabel, textstyle, linebreak,
656 bar, formattext, bold, strong, italic, emphasis, big, small, insertedtext, deletedtext, citation, code, definition, keyboard, monospaced, quotation, sample, variable, bidioverride, strikethrough, subscript, superscript, underline, span,
657 bar, fontstyle, space, fontsize, bar, formatblock, insertparagraphbefore, insertparagraphafter, blockquote, line,
658 bar, left, center, right, justifyfull,
659 bar, orderedlist, unorderedlist, definitionlist, definitionitem, outdent, indent, bar, lefttoright, righttoleft, language, showlanguagemarks,
660 bar, textcolor, bgcolor, textindicator,
661 bar, editelement, showmicrodata, emoticon, insertcharacter, insertsofthyphen, link, unlink, image, table,' . ($this->thisConfig['hideTableOperationsInToolbar'] && is_array($this->thisConfig['buttons.']) && is_array($this->thisConfig['buttons.']['toggleborders.']) && $this->thisConfig['buttons.']['toggleborders.']['keepInToolbar'] ? ' toggleborders,' : '') . ' user, acronym, bar, findreplace, spellcheck,
662 bar, chMode, inserttag, removeformat, bar, copy, cut, paste, pastetoggle, pastebehaviour, bar, undo, redo, bar, showhelp, about, linebreak,
663 ' . ($this->thisConfig['hideTableOperationsInToolbar'] ? '' : 'bar, toggleborders,') . ' bar, tableproperties, tablerestyle, bar, rowproperties, rowinsertabove, rowinsertunder, rowdelete, rowsplit, bar,
664 columnproperties, columninsertbefore, columninsertafter, columndelete, columnsplit, bar,
665 cellproperties, cellinsertbefore, cellinsertafter, celldelete, cellsplit, cellmerge';
666 // Additional buttons from registered plugins
667 foreach ($this->registeredPlugins as $pluginId => $plugin) {
668 if ($this->isPluginEnabled($pluginId)) {
669 $this->defaultToolbarOrder = $plugin->addButtonsToToolbar();
670 }
671 }
672 $toolbarOrder = $this->thisConfig['toolbarOrder'] ?: $this->defaultToolbarOrder;
673 // Getting rid of undefined buttons
674 $this->toolbarOrderArray = array_intersect(GeneralUtility::trimExplode(',', $toolbarOrder, TRUE), GeneralUtility::trimExplode(',', $this->defaultToolbarOrder, TRUE));
675 $toolbarOrder = array_unique(array_values($this->toolbarOrderArray));
676 // Fetching specConf for field from backend
677 $pList = is_array($this->specConf['richtext']['parameters']) ? implode(',', $this->specConf['richtext']['parameters']) : '';
678 if ($pList != '*') {
679 // If not all
680 $show = is_array($this->specConf['richtext']['parameters']) ? $this->specConf['richtext']['parameters'] : array();
681 if ($this->thisConfig['showButtons']) {
682 if (!GeneralUtility::inList($this->thisConfig['showButtons'], '*')) {
683 $show = array_unique(array_merge($show, GeneralUtility::trimExplode(',', $this->thisConfig['showButtons'], TRUE)));
684 } else {
685 $show = array_unique(array_merge($show, $toolbarOrder));
686 }
687 }
688 if (is_array($this->thisConfig['showButtons.'])) {
689 foreach ($this->thisConfig['showButtons.'] as $buttonId => $value) {
690 if ($value) {
691 $show[] = $buttonId;
692 }
693 }
694 $show = array_unique($show);
695 }
696 } else {
697 $show = $toolbarOrder;
698 }
699 // Resticting to RTEkeyList for backend user
700 if (is_object($GLOBALS['BE_USER'])) {
701 $RTEkeyList = isset($GLOBALS['BE_USER']->userTS['options.']['RTEkeyList']) ? $GLOBALS['BE_USER']->userTS['options.']['RTEkeyList'] : '*';
702 if ($RTEkeyList != '*') {
703 // If not all
704 $show = array_intersect($show, GeneralUtility::trimExplode(',', $RTEkeyList, TRUE));
705 }
706 }
707 // Hiding buttons of disabled plugins
708 $hideButtons = array('space', 'bar', 'linebreak');
709 foreach ($this->pluginButton as $pluginId => $buttonList) {
710 if (!$this->isPluginEnabled($pluginId)) {
711 $buttonArray = GeneralUtility::trimExplode(',', $buttonList, TRUE);
712 foreach ($buttonArray as $button) {
713 $hideButtons[] = $button;
714 }
715 }
716 }
717 // Hiding labels of disabled plugins
718 foreach ($this->pluginLabel as $pluginId => $label) {
719 if (!$this->isPluginEnabled($pluginId)) {
720 $hideButtons[] = $label;
721 }
722 }
723 // Hiding buttons
724 $show = array_diff($show, $this->conf_toolbar_hide, GeneralUtility::trimExplode(',', $this->thisConfig['hideButtons'], TRUE));
725 // Apply toolbar constraints from registered plugins
726 foreach ($this->registeredPlugins as $pluginId => $plugin) {
727 if ($this->isPluginEnabled($pluginId) && method_exists($plugin, 'applyToolbarConstraints')) {
728 $show = $plugin->applyToolbarConstraints($show);
729 }
730 }
731 // Getting rid of the buttons for which we have no position
732 $show = array_intersect($show, $toolbarOrder);
733 $this->toolbar = $show;
734 }
735
736 /**
737 * Disable some plugins
738 */
739 public function setPlugins() {
740 // Disabling a plugin that adds buttons if none of its buttons is in the toolbar
741 $hidePlugins = array();
742 foreach ($this->pluginButton as $pluginId => $buttonList) {
743 if ($this->registeredPlugins[$pluginId]->addsButtons()) {
744 $showPlugin = FALSE;
745 $buttonArray = GeneralUtility::trimExplode(',', $buttonList, TRUE);
746 foreach ($buttonArray as $button) {
747 if (in_array($button, $this->toolbar)) {
748 $showPlugin = TRUE;
749 }
750 }
751 if (!$showPlugin) {
752 $hidePlugins[] = $pluginId;
753 }
754 }
755 }
756 $this->pluginEnabledArray = array_diff($this->pluginEnabledArray, $hidePlugins);
757 // Hiding labels of disabled plugins
758 $hideLabels = array();
759 foreach ($this->pluginLabel as $pluginId => $label) {
760 if (!$this->isPluginEnabled($pluginId)) {
761 $hideLabels[] = $label;
762 }
763 }
764 $this->toolbar = array_diff($this->toolbar, $hideLabels);
765 // Adding plugins declared as prerequisites by enabled plugins
766 $requiredPlugins = array();
767 foreach ($this->registeredPlugins as $pluginId => $plugin) {
768 if ($this->isPluginEnabled($pluginId)) {
769 $requiredPlugins = array_merge($requiredPlugins, GeneralUtility::trimExplode(',', $plugin->getRequiredPlugins(), TRUE));
770 }
771 }
772 $requiredPlugins = array_unique($requiredPlugins);
773 foreach ($requiredPlugins as $pluginId) {
774 if (is_object($this->registeredPlugins[$pluginId]) && !$this->isPluginEnabled($pluginId)) {
775 $this->pluginEnabledArray[] = $pluginId;
776 }
777 }
778 $this->pluginEnabledArray = array_unique($this->pluginEnabledArray);
779 // Completing the toolbar conversion array for htmlArea
780 foreach ($this->registeredPlugins as $pluginId => $plugin) {
781 if ($this->isPluginEnabled($pluginId)) {
782 $this->convertToolbarForHtmlAreaArray = array_unique(array_merge($this->convertToolbarForHtmlAreaArray, $plugin->getConvertToolbarForHtmlAreaArray()));
783 }
784 }
785 }
786
787 /**
788 * Convert the TYPO3 names of buttons into the names for htmlArea RTE
789 *
790 * @param string buttonname (typo3-name)
791 * @return string buttonname (htmlarea-name)
792 */
793 public function convertToolbarForHTMLArea($button) {
794 return $this->convertToolbarForHtmlAreaArray[$button];
795 }
796
797 /**
798 * Add RTE main scripts and plugin scripts
799 *
800 * @param string $RTEcounter: The index number of the current RTE editing area within the form.
801 * @return void
802 */
803 protected function addRteJsFiles($RTEcounter) {
804 // Component files. Order is important.
805 $components = array(
806 'Util/Wrap.open',
807 'NameSpace/NameSpace',
808 'UserAgent/UserAgent',
809 'HTMLArea',
810 'Configuration/HTMLArea.Config',
811 'Extjs/ux/Ext.ux.HTMLAreaButton',
812 'Extjs/ux/Ext.ux.Toolbar.HTMLAreaToolbarText',
813 'Extjs/ux/Ext.ux.form.HTMLAreaCombo',
814 'Editor/HTMLArea.Toolbar',
815 'Editor/HTMLArea.Iframe',
816 'Editor/HTMLArea.StatusBar',
817 'Editor/HTMLArea.Framework',
818 'Editor/HTMLArea.Editor',
819 'Ajax/HTMLArea.Ajax',
820 'Util/HTMLArea.util.TYPO3',
821 'Util/HTMLArea.util',
822 'DOM/HTMLArea.DOM',
823 'DOM/HTMLArea.DOM.Walker',
824 'DOM/HTMLArea.DOM.Selection',
825 'DOM/HTMLArea.DOM.BookMark',
826 'DOM/HTMLArea.DOM.Node',
827 'CSS/HTMLArea.CSS.Parser',
828 'Util/HTMLArea.util.Tips',
829 'Util/HTMLArea.util.Color',
830 'Extjs/Ext.ColorPalette',
831 'Extjs/ux/Ext.ux.menu.HTMLAreaColorMenu',
832 'Extjs/ux/Ext.ux.form.ColorPaletteField',
833 'LoremIpsum',
834 'plugins/Plugin'
835 );
836 foreach ($components as $component) {
837 $this->pageRenderer->addJsFile($this->getFullFileName('EXT:' . $this->ID . '/htmlarea/' . $component . '.js'));
838 }
839 foreach ($this->pluginEnabledCumulativeArray[$RTEcounter] as $pluginId) {
840 $extensionKey = is_object($this->registeredPlugins[$pluginId]) ? $this->registeredPlugins[$pluginId]->getExtensionKey() : $this->ID;
841 $this->pageRenderer->addJsFile($this->getFullFileName('EXT:' . $extensionKey . '/htmlarea/plugins/' . $pluginId . '/' . strtolower(preg_replace('/([a-z])([A-Z])([a-z])/', '$1-$2$3', $pluginId)) . '.js'));
842 }
843 $this->pageRenderer->addJsFile($this->getFullFileName('EXT:' . $this->ID . '/htmlarea/Util/Wrap.close.js'));
844 }
845
846 /**
847 * Return RTE initialization inline JavaScript code
848 *
849 * @return string RTE initialization inline JavaScript code
850 */
851 protected function getRteInitJsCode() {
852 return '
853 if (typeof RTEarea === "undefined") {
854 RTEarea = new Object();
855 RTEarea[0] = new Object();
856 RTEarea[0].version = "' . $GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->ID]['version'] . '";
857 RTEarea[0].editorUrl = "' . $this->extHttpPath . 'htmlarea/";
858 RTEarea[0].editorCSS = "' . GeneralUtility::createVersionNumberedFilename($this->editorCSS) . '";
859 RTEarea[0].editorSkin = "' . dirname($this->editorCSS) . '/";
860 RTEarea[0].editedContentCSS = "' . GeneralUtility::createVersionNumberedFilename($this->editedContentCSS) . '";
861 RTEarea[0].hostUrl = "' . $this->hostURL . '";
862 RTEarea.init = function() {
863 if (typeof HTMLArea === "undefined" || !Ext.isReady) {
864 window.setTimeout("RTEarea.init();", 10);
865 } else {
866 Ext.QuickTips.init();
867 HTMLArea.init();
868 }
869 };
870 RTEarea.initEditor = function(editorNumber) {
871 if (typeof HTMLArea === "undefined" || !HTMLArea.isReady) {
872 RTEarea.initEditor.defer(40, null, [editorNumber]);
873 } else {
874 HTMLArea.initEditor(editorNumber);
875 }
876 };
877 }
878 RTEarea.init();';
879 }
880
881 /**
882 * Return the Javascript code for configuring the RTE
883 *
884 * @param int $RTEcounter: The index number of the current RTE editing area within the form.
885 * @param string $table: The table that includes this RTE (optional, necessary for IRRE).
886 * @param string $uid: The uid of that table that includes this RTE (optional, necessary for IRRE).
887 * @param string $field: The field of that record that includes this RTE (optional).
888 * @param string $textAreaId ID of the textarea, to have a unigue number for the editor
889 * @return string the Javascript code for configuring the RTE
890 */
891 public function registerRTEinJS($RTEcounter, $table = '', $uid = '', $field = '', $textAreaId = '') {
892 $configureRTEInJavascriptString = '
893 if (typeof configureEditorInstance === "undefined") {
894 configureEditorInstance = new Object();
895 }
896 configureEditorInstance["' . $textAreaId . '"] = function() {
897 if (typeof RTEarea === "undefined" || typeof HTMLArea === "undefined") {
898 window.setTimeout("configureEditorInstance[\'' . $textAreaId . '\']();", 40);
899 } else {
900 editornumber = "' . $textAreaId . '";
901 RTEarea[editornumber] = new Object();
902 RTEarea[editornumber].RTEtsConfigParams = "&RTEtsConfigParams=' . rawurlencode($this->RTEtsConfigParams()) . '";
903 RTEarea[editornumber].number = editornumber;
904 RTEarea[editornumber].deleted = false;
905 RTEarea[editornumber].textAreaId = "' . $textAreaId . '";
906 RTEarea[editornumber].id = "RTEarea" + editornumber;
907 RTEarea[editornumber].RTEWidthOverride = "' . (is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteWidth']) && trim($GLOBALS['BE_USER']->uc['rteWidth']) ? trim($GLOBALS['BE_USER']->uc['rteWidth']) : trim($this->thisConfig['RTEWidthOverride'])) . '";
908 RTEarea[editornumber].RTEHeightOverride = "' . (is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteHeight']) && (int)$GLOBALS['BE_USER']->uc['rteHeight'] ? (int)$GLOBALS['BE_USER']->uc['rteHeight'] : (int)$this->thisConfig['RTEHeightOverride']) . '";
909 RTEarea[editornumber].resizable = ' . (is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteResize']) && $GLOBALS['BE_USER']->uc['rteResize'] ? 'true' : (trim($this->thisConfig['rteResize']) ? 'true' : 'false')) . ';
910 RTEarea[editornumber].maxHeight = "' . (is_object($GLOBALS['BE_USER']) && isset($GLOBALS['BE_USER']->uc['rteMaxHeight']) && (int)$GLOBALS['BE_USER']->uc['rteMaxHeight'] ? trim($GLOBALS['BE_USER']->uc['rteMaxHeight']) : ((int)$this->thisConfig['rteMaxHeight'] ?: '2000')) . '";
911 RTEarea[editornumber].fullScreen = ' . ($this->fullScreen ? 'true' : 'false') . ';
912 RTEarea[editornumber].showStatusBar = ' . (trim($this->thisConfig['showStatusBar']) ? 'true' : 'false') . ';
913 RTEarea[editornumber].enableWordClean = ' . (trim($this->thisConfig['enableWordClean']) ? 'true' : 'false') . ';
914 RTEarea[editornumber].htmlRemoveComments = ' . (trim($this->thisConfig['removeComments']) ? 'true' : 'false') . ';
915 RTEarea[editornumber].disableEnterParagraphs = ' . (trim($this->thisConfig['disableEnterParagraphs']) ? 'true' : 'false') . ';
916 RTEarea[editornumber].disableObjectResizing = ' . (trim($this->thisConfig['disableObjectResizing']) ? 'true' : 'false') . ';
917 RTEarea[editornumber].removeTrailingBR = ' . (trim($this->thisConfig['removeTrailingBR']) ? 'true' : 'false') . ';
918 RTEarea[editornumber].useCSS = ' . (trim($this->thisConfig['useCSS']) ? 'true' : 'false') . ';
919 RTEarea[editornumber].keepButtonGroupTogether = ' . (trim($this->thisConfig['keepButtonGroupTogether']) ? 'true' : 'false') . ';
920 RTEarea[editornumber].disablePCexamples = ' . (trim($this->thisConfig['disablePCexamples']) ? 'true' : 'false') . ';
921 RTEarea[editornumber].showTagFreeClasses = ' . (trim($this->thisConfig['showTagFreeClasses']) ? 'true' : 'false') . ';
922 RTEarea[editornumber].useHTTPS = ' . (trim(stristr($this->siteURL, 'https')) || $this->thisConfig['forceHTTPS'] ? 'true' : 'false') . ';
923 RTEarea[editornumber].tceformsNested = ' . (is_object($this->TCEform) && method_exists($this->TCEform, 'getDynNestedStack') ? $this->TCEform->getDynNestedStack(TRUE) : '[]') . ';
924 RTEarea[editornumber].dialogueWindows = new Object();';
925 if (isset($this->thisConfig['dialogueWindows.']['defaultPositionFromTop'])) {
926 $configureRTEInJavascriptString .= '
927 RTEarea[editornumber].dialogueWindows.positionFromTop = ' . (int)$this->thisConfig['dialogueWindows.']['defaultPositionFromTop'] . ';';
928 }
929 if (isset($this->thisConfig['dialogueWindows.']['defaultPositionFromLeft'])) {
930 $configureRTEInJavascriptString .= '
931 RTEarea[editornumber].dialogueWindows.positionFromLeft = ' . (int)$this->thisConfig['dialogueWindows.']['defaultPositionFromLeft'] . ';';
932 }
933 // The following properties apply only to the backend
934 if (!$this->is_FE()) {
935 $configureRTEInJavascriptString .= '
936 RTEarea[editornumber].sys_language_content = "' . $this->contentLanguageUid . '";
937 RTEarea[editornumber].typo3ContentLanguage = "' . $this->contentTypo3Language . '";
938 RTEarea[editornumber].typo3ContentCharset = "' . $this->contentCharset . '";
939 RTEarea[editornumber].userUid = "' . $this->userUid . '";';
940 }
941 // Setting the plugin flags
942 $configureRTEInJavascriptString .= '
943 RTEarea[editornumber].plugin = new Object();
944 RTEarea[editornumber].pathToPluginDirectory = new Object();';
945 foreach ($this->pluginEnabledArray as $pluginId) {
946 $configureRTEInJavascriptString .= '
947 RTEarea[editornumber].plugin.' . $pluginId . ' = true;';
948 if (is_object($this->registeredPlugins[$pluginId])) {
949 $pathToPluginDirectory = $this->registeredPlugins[$pluginId]->getPathToPluginDirectory();
950 if ($pathToPluginDirectory) {
951 $configureRTEInJavascriptString .= '
952 RTEarea[editornumber].pathToPluginDirectory.' . $pluginId . ' = "' . $pathToPluginDirectory . '";';
953 }
954 }
955 }
956 // Setting the buttons configuration
957 $configureRTEInJavascriptString .= '
958 RTEarea[editornumber].buttons = new Object();';
959 if (is_array($this->thisConfig['buttons.'])) {
960 foreach ($this->thisConfig['buttons.'] as $buttonIndex => $conf) {
961 $button = substr($buttonIndex, 0, -1);
962 if (is_array($conf)) {
963 $configureRTEInJavascriptString .= '
964 RTEarea[editornumber].buttons.' . $button . ' = ' . $this->buildNestedJSArray($conf) . ';';
965 }
966 }
967 }
968 // Setting the list of tags to be removed if specified in the RTE config
969 if (trim($this->thisConfig['removeTags'])) {
970 $configureRTEInJavascriptString .= '
971 RTEarea[editornumber].htmlRemoveTags = /^(' . implode('|', GeneralUtility::trimExplode(',', $this->thisConfig['removeTags'], TRUE)) . ')$/i;';
972 }
973 // Setting the list of tags to be removed with their contents if specified in the RTE config
974 if (trim($this->thisConfig['removeTagsAndContents'])) {
975 $configureRTEInJavascriptString .= '
976 RTEarea[editornumber].htmlRemoveTagsAndContents = /^(' . implode('|', GeneralUtility::trimExplode(',', $this->thisConfig['removeTagsAndContents'], TRUE)) . ')$/i;';
977 }
978 // Setting array of custom tags if specified in the RTE config
979 if (!empty($this->thisConfig['customTags'])) {
980 $customTags = GeneralUtility::trimExplode(',', $this->thisConfig['customTags'], TRUE);
981 if (!empty($customTags)) {
982 $configureRTEInJavascriptString .= '
983 RTEarea[editornumber].customTags= ' . json_encode($customTags) . ';';
984 }
985 }
986 // Setting array of content css files if specified in the RTE config
987 $versionNumberedFileNames = array();
988 $contentCssFileNames = $this->getContentCssFileNames();
989 foreach ($contentCssFileNames as $contentCssFileName) {
990 $versionNumberedFileNames[] = GeneralUtility::createVersionNumberedFilename($contentCssFileName);
991 }
992 $configureRTEInJavascriptString .= '
993 RTEarea[editornumber].pageStyle = ["' . implode('","', $versionNumberedFileNames) . '"];';
994 // Process classes configuration
995 $classesConfigurationRequired = FALSE;
996 foreach ($this->registeredPlugins as $pluginId => $plugin) {
997 if ($this->isPluginEnabled($pluginId)) {
998 $classesConfigurationRequired = $classesConfigurationRequired || $plugin->requiresClassesConfiguration();
999 }
1000 }
1001 if ($classesConfigurationRequired) {
1002 $configureRTEInJavascriptString .= $this->buildJSClassesConfig($RTEcounter);
1003 }
1004 // Add Javascript configuration for registered plugins
1005 foreach ($this->registeredPlugins as $pluginId => $plugin) {
1006 if ($this->isPluginEnabled($pluginId)) {
1007 $configureRTEInJavascriptString .= $plugin->buildJavascriptConfiguration('editornumber');
1008 }
1009 }
1010 // Avoid premature reference to HTMLArea when being initially loaded by IRRE Ajax call
1011 $configureRTEInJavascriptString .= '
1012 RTEarea[editornumber].toolbar = ' . $this->getJSToolbarArray() . ';
1013 RTEarea[editornumber].convertButtonId = ' . json_encode(array_flip($this->convertToolbarForHtmlAreaArray)) . ';
1014 RTEarea.initEditor(editornumber);
1015 }
1016 };
1017 configureEditorInstance["' . $textAreaId . '"]();';
1018 return $configureRTEInJavascriptString;
1019 }
1020
1021 /**
1022 * Return TRUE, if the plugin can be loaded
1023 *
1024 * @param string $pluginId: The identification string of the plugin
1025 * @return bool TRUE if the plugin can be loaded
1026 */
1027 public function isPluginEnabled($pluginId) {
1028 return in_array($pluginId, $this->pluginEnabledArray);
1029 }
1030
1031 /**
1032 * Build the default content style sheet
1033 *
1034 * @return string Style sheet
1035 * @deprecated since TYPO3 6.0, will be removed in TYPO3 6.2
1036 */
1037 public function buildStyleSheet() {
1038 $stylesheet = '/* mainStyleOverride and inlineStyle properties ignored. */';
1039 return $stylesheet;
1040 }
1041
1042 /**
1043 * Return Javascript configuration of classes
1044 *
1045 * @param int $RTEcounter: The index number of the current RTE editing area within the form.
1046 * @return string Javascript configuration of classes
1047 */
1048 public function buildJSClassesConfig($RTEcounter) {
1049 // Include JS arrays of configured classes
1050 $configureRTEInJavascriptString .= '
1051 RTEarea[editornumber].classesUrl = "' . ($this->is_FE() && $GLOBALS['TSFE']->absRefPrefix ? $GLOBALS['TSFE']->absRefPrefix : '') . $this->writeTemporaryFile('', ('classes_' . $this->language), 'js', $this->buildJSClassesArray(), TRUE) . '";';
1052 return $configureRTEInJavascriptString;
1053 }
1054
1055 /**
1056 * Return JS arrays of classes configuration
1057 *
1058 * @return string JS classes arrays
1059 */
1060 public function buildJSClassesArray() {
1061 if ($this->is_FE()) {
1062 $RTEProperties = $this->RTEsetup;
1063 } else {
1064 $RTEProperties = $this->RTEsetup['properties'];
1065 }
1066 $classesArray = array('labels' => array(), 'values' => array(), 'noShow' => array(), 'alternating' => array(), 'counting' => array(), 'selectable' => array(), 'XOR' => array());
1067 $JSClassesArray = '';
1068 // Scanning the list of classes if specified in the RTE config
1069 if (is_array($RTEProperties['classes.'])) {
1070 foreach ($RTEProperties['classes.'] as $className => $conf) {
1071 $className = rtrim($className, '.');
1072 $classesArray['labels'][$className] = $this->getPageConfigLabel($conf['name'], FALSE);
1073 $classesArray['values'][$className] = str_replace('\\\'', '\'', $conf['value']);
1074 if (isset($conf['noShow'])) {
1075 $classesArray['noShow'][$className] = $conf['noShow'];
1076 }
1077 if (is_array($conf['alternating.'])) {
1078 $classesArray['alternating'][$className] = $conf['alternating.'];
1079 }
1080 if (is_array($conf['counting.'])) {
1081 $classesArray['counting'][$className] = $conf['counting.'];
1082 }
1083 if (isset($conf['selectable'])) {
1084 $classesArray['selectable'][$className] = $conf['selectable'];
1085 }
1086 }
1087 }
1088 // Scanning the list of sets of mutually exclusives classes if specified in the RTE config
1089 if (is_array($RTEProperties['mutuallyExclusiveClasses.'])) {
1090 foreach ($RTEProperties['mutuallyExclusiveClasses.'] as $listName => $conf) {
1091 $classSet = GeneralUtility::trimExplode(',', $conf, TRUE);
1092 $classList = implode(',', $classSet);
1093 foreach ($classSet as $className) {
1094 $classesArray['XOR'][$className] = '/^(' . implode('|', GeneralUtility::trimExplode(',', GeneralUtility::rmFromList($className, $classList), TRUE)) . ')$/';
1095 }
1096 }
1097 }
1098 foreach ($classesArray as $key => $subArray) {
1099 $JSClassesArray .= 'HTMLArea.classes' . ucfirst($key) . ' = ' . $this->buildNestedJSArray($subArray) . ';' . LF;
1100 }
1101 return $JSClassesArray;
1102 }
1103
1104 /**
1105 * Translate Page TS Config array in JS nested array definition
1106 * Replace 0 values with false
1107 * Unquote regular expression values
1108 * Replace empty arrays with empty objects
1109 *
1110 * @param array $conf: Page TSConfig configuration array
1111 * @return string nested JS array definition
1112 */
1113 public function buildNestedJSArray($conf) {
1114 $convertedConf = GeneralUtility::removeDotsFromTS($conf);
1115 return str_replace(array(':"0"', ':"\\/^(', ')$\\/i"', ':"\\/^(', ')$\\/"', '[]'), array(':false', ':/^(', ')$/i', ':/^(', ')$/', '{}'), json_encode($convertedConf));
1116 }
1117
1118 /**
1119 * Return a Javascript localization array for htmlArea RTE
1120 *
1121 * @return string Javascript localization array
1122 */
1123 public function buildJSMainLangArray() {
1124 $JSLanguageArray = 'HTMLArea.I18N = new Object();' . LF;
1125 $labelsArray = array('tooltips' => array(), 'msg' => array(), 'dialogs' => array());
1126 foreach ($labelsArray as $labels => $subArray) {
1127 $LOCAL_LANG = GeneralUtility::readLLfile('EXT:' . $this->ID . '/htmlarea/locallang_' . $labels . '.xlf', $this->language, 'utf-8');
1128 if (!empty($LOCAL_LANG[$this->language])) {
1129 $mergedLocalLang = $LOCAL_LANG['default'];
1130 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($mergedLocalLang, $LOCAL_LANG[$this->language], TRUE, FALSE);
1131 $LOCAL_LANG[$this->language] = $mergedLocalLang;
1132 } else {
1133 $LOCAL_LANG[$this->language] = $LOCAL_LANG['default'];
1134 }
1135 $labelsArray[$labels] = $LOCAL_LANG[$this->language];
1136 }
1137 $JSLanguageArray .= 'HTMLArea.I18N = ' . json_encode($labelsArray) . ';' . LF;
1138 return $JSLanguageArray;
1139 }
1140
1141 /**
1142 * Writes contents in a file in typo3temp/rtehtmlarea directory and returns the file name
1143 *
1144 * @param string $sourceFileName: The name of the file from which the contents should be extracted
1145 * @param string $label: A label to insert at the beginning of the name of the file
1146 * @param string $fileExtension: The file extension of the file, defaulting to 'js'
1147 * @param string $contents: The contents to write into the file if no $sourceFileName is provided
1148 * @param bool $concatenate Not used anymore
1149 * @return string The name of the file writtten to typo3temp/rtehtmlarea
1150 */
1151 public function writeTemporaryFile($sourceFileName = '', $label, $fileExtension = 'js', $contents = '', $concatenate = FALSE) {
1152 if ($sourceFileName) {
1153 $output = '';
1154 $source = GeneralUtility::getFileAbsFileName($sourceFileName);
1155 $output = file_get_contents($source);
1156 } else {
1157 $output = $contents;
1158 }
1159 $relativeFilename = 'typo3temp/' . $this->ID . '_' . str_replace('-', '_', $label) . '_' . GeneralUtility::shortMD5((TYPO3_version . $TYPO3_CONF_VARS['EXTCONF'][$this->ID]['version'] . ($sourceFileName ? $sourceFileName : $output)), 20) . '.' . $fileExtension;
1160 $destination = PATH_site . $relativeFilename;
1161 if (!file_exists($destination)) {
1162 $minifiedJavaScript = '';
1163 if ($fileExtension == 'js' && $output != '') {
1164 $minifiedJavaScript = GeneralUtility::minifyJavaScript($output);
1165 }
1166 $failure = GeneralUtility::writeFileToTypo3tempDir($destination, $minifiedJavaScript ? $minifiedJavaScript : $output);
1167 if ($failure) {
1168 throw new \RuntimeException($failure, 1294585668);
1169 }
1170 }
1171 if ($this->is_FE()) {
1172 $filename = $relativeFilename;
1173 } else {
1174 $filename = ($this->isFrontendEditActive() ? '' : $this->backPath . '../') . $relativeFilename;
1175 }
1176 return GeneralUtility::resolveBackPath($filename);
1177 }
1178
1179 /**
1180 * Return a file name containing the main JS language array for HTMLArea
1181 *
1182 * @param int $RTEcounter: The index number of the current RTE editing area within the form.
1183 * @return string filename
1184 */
1185 public function buildJSMainLangFile($RTEcounter) {
1186 $contents = $this->buildJSMainLangArray() . LF;
1187 foreach ($this->pluginEnabledCumulativeArray[$RTEcounter] as $pluginId) {
1188 $contents .= $this->buildJSLangArray($pluginId) . LF;
1189 }
1190 return $this->writeTemporaryFile('', $this->language . '_' . $this->OutputCharset, 'js', $contents, TRUE);
1191 }
1192
1193 /**
1194 * Return a Javascript localization array for the plugin
1195 *
1196 * @param string $plugin: identification string of the plugin
1197 * @return string Javascript localization array
1198 */
1199 public function buildJSLangArray($plugin) {
1200 $extensionKey = is_object($this->registeredPlugins[$plugin]) ? $this->registeredPlugins[$plugin]->getExtensionKey() : $this->ID;
1201 $LOCAL_LANG = GeneralUtility::readLLfile('EXT:' . $extensionKey . '/htmlarea/plugins/' . $plugin . '/locallang.xlf', $this->language, 'utf-8', 1);
1202 $JSLanguageArray = 'HTMLArea.I18N["' . $plugin . '"] = new Object();' . LF;
1203 if (is_array($LOCAL_LANG)) {
1204 if (!empty($LOCAL_LANG[$this->language])) {
1205 $defaultLocalLang = $LOCAL_LANG['default'];
1206 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($defaultLocalLang, $LOCAL_LANG[$this->language], TRUE, FALSE);
1207 $LOCAL_LANG[$this->language] = $defaultLocalLang;
1208 } else {
1209 $LOCAL_LANG[$this->language] = $LOCAL_LANG['default'];
1210 }
1211 $JSLanguageArray .= 'HTMLArea.I18N["' . $plugin . '"] = ' . json_encode($LOCAL_LANG[$this->language]) . ';' . LF;
1212 }
1213 return $JSLanguageArray;
1214 }
1215
1216 /**
1217 * Return the JS code of the toolbar configuration for the HTMLArea editor
1218 *
1219 * @return string the JS code as nested JS arrays
1220 */
1221 protected function getJSToolbarArray() {
1222 // The toolbar array
1223 $toolbar = array();
1224 // The current row; a "linebreak" ends the current row
1225 $row = array();
1226 // The current group; each group is between "bar"s; a "linebreak" ends the current group
1227 $group = array();
1228 // Process each toolbar item in the toolbar order list
1229 foreach ($this->toolbarOrderArray as $item) {
1230 switch ($item) {
1231 case 'linebreak':
1232 // Add row to toolbar if not empty
1233 if (!empty($group)) {
1234 $row[] = $group;
1235 $group = array();
1236 }
1237 if (!empty($row)) {
1238 $toolbar[] = $row;
1239 $row = array();
1240 }
1241 break;
1242 case 'bar':
1243 // Add group to row if not empty
1244 if (!empty($group)) {
1245 $row[] = $group;
1246 $group = array();
1247 }
1248 break;
1249 case 'space':
1250 if (end($group) != $this->convertToolbarForHTMLArea($item)) {
1251 $group[] = $this->convertToolbarForHTMLArea($item);
1252 }
1253 break;
1254 default:
1255 if (in_array($item, $this->toolbar)) {
1256 // Add the item to the group
1257 $convertedItem = $this->convertToolbarForHTMLArea($item);
1258 if ($convertedItem) {
1259 $group[] = $convertedItem;
1260 }
1261 }
1262 }
1263 }
1264 // Add the last group and last line, if not empty
1265 if (!empty($group)) {
1266 $row[] = $group;
1267 }
1268 if (!empty($row)) {
1269 $toolbar[] = $row;
1270 }
1271 return json_encode($toolbar);
1272 }
1273
1274 /**
1275 * Localize a string using the language of the content element rather than the language of the BE interface
1276 *
1277 * @param string string: the label to be localized
1278 * @return string Localized string.
1279 */
1280 public function getLLContent($string) {
1281 return GeneralUtility::quoteJSvalue($this->contentLanguageService->sL($string));
1282 }
1283
1284 public function getPageConfigLabel($string, $JScharCode = 1) {
1285 global $LANG, $TSFE, $TYPO3_CONF_VARS;
1286 if ($this->is_FE()) {
1287 if (substr($string, 0, 4) !== 'LLL:') {
1288 // A pure string coming from Page TSConfig must be in utf-8
1289 $label = $TSFE->csConvObj->conv($TSFE->sL(trim($string)), 'utf-8', $this->OutputCharset);
1290 } else {
1291 $label = $TSFE->csConvObj->conv($TSFE->sL(trim($string)), $this->charset, $this->OutputCharset);
1292 }
1293 $label = str_replace('"', '\\"', str_replace('\\\'', '\'', $label));
1294 $label = $JScharCode ? $this->feJScharCode($label) : $label;
1295 } else {
1296 if (substr($string, 0, 4) !== 'LLL:') {
1297 $label = $string;
1298 } else {
1299 $label = $LANG->sL(trim($string));
1300 }
1301 $label = str_replace('"', '\\"', str_replace('\\\'', '\'', $label));
1302 $label = $JScharCode ? GeneralUtility::quoteJSvalue($label) : $label;
1303 }
1304 return $label;
1305 }
1306
1307 /**
1308 * JavaScript char code
1309 *
1310 * @param string $str
1311 * @return string
1312 */
1313 public function feJScharCode($str) {
1314 // Convert string to UTF-8:
1315 if ($this->OutputCharset != 'utf-8') {
1316 $str = $GLOBALS['TSFE']->csConvObj->utf8_encode($str, $this->OutputCharset);
1317 }
1318 // Convert the UTF-8 string into a 'JavaScript-safe' encoded string:
1319 return GeneralUtility::quoteJSvalue($str);
1320 }
1321
1322 /**
1323 * Make a file name relative to the PATH_site or to the PATH_typo3
1324 *
1325 * @param string $filename: a file name of the form EXT:.... or relative to the PATH_site
1326 * @return string the file name relative to the PATH_site if in frontend or relative to the PATH_typo3 if in backend
1327 */
1328 public function getFullFileName($filename) {
1329 if (substr($filename, 0, 4) == 'EXT:') {
1330 // extension
1331 list($extKey, $local) = explode('/', substr($filename, 4), 2);
1332 $newFilename = '';
1333 if ((string)$extKey !== '' && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($extKey) && (string)$local !== '') {
1334 $newFilename = ($this->is_FE() || $this->isFrontendEditActive() ? \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($extKey) : $this->backPath . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($extKey)) . $local;
1335 }
1336 } else {
1337 $path = ($this->is_FE() || $this->isFrontendEditActive() ? '' : $this->backPath . '../');
1338 $newFilename = $path . ($filename[0] === '/' ? substr($filename, 1) : $filename);
1339 }
1340 return GeneralUtility::resolveBackPath($newFilename);
1341 }
1342
1343 /**
1344 * Return the Javascript code for copying the HTML code from the editor into the hidden input field.
1345 * This is for submit function of the form.
1346 *
1347 * @param int $RTEcounter: The index number of the current RTE editing area within the form.
1348 * @param string $formName: the name of the form
1349 * @param string $textareaId: the id of the textarea
1350 * @param string $textareaName: the name of the textarea
1351 * @return string Javascript code
1352 */
1353 public function setSaveRTE($RTEcounter, $formName, $textareaId, $textareaName) {
1354 return 'if (RTEarea["' . $textareaId . '"]) { document.' . $formName . '["' . $textareaName . '"].value = RTEarea["' . $textareaId . '"].editor.getHTML(); } else { OK = 0; };';
1355 }
1356
1357 /**
1358 * Return the Javascript code for copying the HTML code from the editor into the hidden input field.
1359 * This is for submit function of the form.
1360 *
1361 * @param int $RTEcounter: The index number of the current RTE editing area within the form.
1362 * @param string $formName: the name of the form
1363 * @param string $textareaId: the id of the textarea
1364 * @return string Javascript code
1365 */
1366 public function setDeleteRTE($RTEcounter, $formName, $textareaId) {
1367 return 'if (RTEarea["' . $textareaId . '"]) { RTEarea["' . $textareaId . '"].deleted = true;}';
1368 }
1369
1370 /**
1371 * Return TRUE if we are in the FE, but not in the FE editing feature of BE.
1372 *
1373 * @return bool
1374 */
1375 public function is_FE() {
1376 return is_object($GLOBALS['TSFE']) && !$this->isFrontendEditActive() && TYPO3_MODE == 'FE';
1377 }
1378
1379 /**
1380 * Checks whether frontend editing is active.
1381 *
1382 * @return boolean
1383 */
1384 public function isFrontendEditActive() {
1385 return is_object($GLOBALS['TSFE']) && $GLOBALS['TSFE']->beUserLogin && $GLOBALS['BE_USER']->frontendEdit instanceof \TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
1386 }
1387
1388 /**
1389 * Client Browser Information
1390 *
1391 * @param string $userAgent: The useragent string, \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('HTTP_USER_AGENT')
1392 * @return array Contains keys "useragent", "browser", "version", "system
1393 */
1394 public function clientInfo($userAgent = '') {
1395 if (!$userAgent) {
1396 $userAgent = GeneralUtility::getIndpEnv('HTTP_USER_AGENT');
1397 }
1398 $browserInfo = \TYPO3\CMS\Core\Utility\ClientUtility::getBrowserInfo($userAgent);
1399 // Known engines: order is not irrelevant!
1400 $knownEngines = array('opera', 'msie', 'gecko', 'webkit');
1401 if (is_array($browserInfo['all'])) {
1402 foreach ($knownEngines as $engine) {
1403 if ($browserInfo['all'][$engine]) {
1404 $browserInfo['browser'] = $engine;
1405 $browserInfo['version'] = \TYPO3\CMS\Core\Utility\ClientUtility::getVersion($browserInfo['all'][$engine]);
1406 break;
1407 }
1408 }
1409 }
1410 return $browserInfo;
1411 }
1412
1413 /**
1414 * Log usage of deprecated Page TS Config Property
1415 *
1416 * @param string $deprecatedProperty: Name of deprecated property
1417 * @param string $useProperty: Name of property to use instead
1418 * @param string $version: Version of TYPO3 in which the property will be removed
1419 * @return void
1420 */
1421 public function logDeprecatedProperty($deprecatedProperty, $useProperty, $version) {
1422 if (!$this->thisConfig['logDeprecatedProperties.']['disabled']) {
1423 $message = sprintf('RTE Page TSConfig property "%1$s" used on page id #%4$s is DEPRECATED and will be removed in TYPO3 %3$s. Use "%2$s" instead.', $deprecatedProperty, $useProperty, $version, $this->thePid);
1424 GeneralUtility::deprecationLog($message);
1425 if (is_object($GLOBALS['BE_USER']) && $this->thisConfig['logDeprecatedProperties.']['logAlsoToBELog']) {
1426 $message = sprintf($GLOBALS['LANG']->getLL('deprecatedPropertyMessage'), $deprecatedProperty, $useProperty, $version, $this->thePid);
1427 $GLOBALS['BE_USER']->simplelog($message, $this->ID);
1428 }
1429 }
1430 }
1431
1432 /***************************
1433 *
1434 * OTHER FUNCTIONS: (from Classic RTE)
1435 *
1436 ***************************/
1437 /**
1438 * @return [type] ...
1439 * @desc
1440 */
1441 public function RTEtsConfigParams() {
1442 if ($this->is_FE()) {
1443 return '';
1444 } else {
1445 $p = BackendUtility::getSpecConfParametersFromArray($this->specConf['rte_transform']['parameters']);
1446 return $this->elementParts[0] . ':' . $this->elementParts[1] . ':' . $this->elementParts[2] . ':' . $this->thePid . ':' . $this->typeVal . ':' . $this->tscPID . ':' . $p['imgpath'];
1447 }
1448 }
1449
1450 public function cleanList($str) {
1451 if (strstr($str, '*')) {
1452 $str = '*';
1453 } else {
1454 $str = implode(',', array_unique(GeneralUtility::trimExplode(',', $str, TRUE)));
1455 }
1456 return $str;
1457 }
1458
1459 /**
1460 * Filter style element
1461 *
1462 * @param string $elValue
1463 * @param string $matchList
1464 * @return string
1465 */
1466 public function filterStyleEl($elValue, $matchList) {
1467 $matchParts = GeneralUtility::trimExplode(',', $matchList, TRUE);
1468 $styleParts = explode(';', $elValue);
1469 $nStyle = array();
1470 foreach ($styleParts as $k => $p) {
1471 $pp = GeneralUtility::trimExplode(':', $p);
1472 if ($pp[0] && $pp[1]) {
1473 foreach ($matchParts as $el) {
1474 $star = substr($el, -1) == '*';
1475 if ($pp[0] === (string)$el || $star && GeneralUtility::isFirstPartOfStr($pp[0], substr($el, 0, -1))) {
1476 $nStyle[] = $pp[0] . ':' . $pp[1];
1477 } else {
1478 unset($styleParts[$k]);
1479 }
1480 }
1481 } else {
1482 unset($styleParts[$k]);
1483 }
1484 }
1485 return implode('; ', $nStyle);
1486 }
1487
1488 // Hook on lorem_ipsum extension to insert text into the RTE in wysiwyg mode
1489 /**
1490 * @deprecated since 6.2 - will be removed two versions later without replacement
1491 */
1492 public function loremIpsumInsert($params) {
1493 GeneralUtility::logDeprecatedFunction();
1494 return '
1495 if (typeof(lorem_ipsum) == \'function\' && ' . $params['element'] . '.tagName.toLowerCase() == \'textarea\' ) lorem_ipsum(' . $params['element'] . ', lipsum_temp_strings[lipsum_temp_pointer]);
1496 ';
1497 }
1498
1499 }