[SECURITY] Fix several XSS issues
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Classes / SelectImage.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\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Resource;
20
21 /**
22 * Script Class
23 *
24 * @author Kasper Skårhøj <kasper@typo3.com>
25 */
26 class SelectImage extends \TYPO3\CMS\Recordlist\Browser\ElementBrowser {
27
28 /**
29 * These file extensions are allowed in the "plain" image selection mode.
30 *
31 * @const
32 */
33 const PLAIN_MODE_IMAGE_FILE_EXTENSIONS = 'jpg,jpeg,gif,png';
34
35 /**
36 * @todo Define visibility
37 */
38 public $extKey = 'rtehtmlarea';
39
40 /**
41 * @todo Define visibility
42 */
43 public $content;
44
45 public $allowedItems;
46
47 public $allowedFileTypes = array();
48
49 protected $defaultClass;
50
51 /**
52 * Relevant for RTE mode "plain": the maximum width an image must have to be selectable.
53 *
54 * @var int
55 */
56 protected $plainMaxWidth;
57
58 /**
59 * Relevant for RTE mode "plain": the maximum height an image must have to be selectable.
60 *
61 * @var int
62 */
63 protected $plainMaxHeight;
64
65 protected $imgPath;
66
67 public $editorNo;
68
69 public $sys_language_content;
70
71 public $thisConfig;
72
73 public $buttonConfig;
74
75 protected $imgObj;
76
77 /**
78 * Initialisation
79 *
80 * @return void
81 */
82 public function init() {
83 $this->initVariables();
84 $this->initConfiguration();
85 $this->initHookObjects('ext/rtehtmlarea/mod4/class.tx_rtehtmlarea_select_image.php');
86
87 $this->allowedItems = $this->getAllowedItems('magic,plain,image');
88 $this->insertImage();
89
90 $this->initDocumentTemplate();
91 }
92
93 /**
94 * Initialize class variables
95 *
96 * @return void
97 */
98 public function initVariables() {
99 parent::initVariables();
100 // Get "act"
101 $this->act = GeneralUtility::_GP('act');
102 if (!$this->act) {
103 $this->act = FALSE;
104 }
105 // Process bparams
106 $pArr = explode('|', $this->bparams);
107 $pRteArr = explode(':', $pArr[1]);
108 $this->editorNo = $pRteArr[0];
109 $this->sys_language_content = $pRteArr[1];
110 $this->RTEtsConfigParams = $pArr[2];
111 if (!$this->editorNo) {
112 $this->editorNo = GeneralUtility::_GP('editorNo');
113 $this->sys_language_content = GeneralUtility::_GP('sys_language_content');
114 $this->RTEtsConfigParams = GeneralUtility::_GP('RTEtsConfigParams');
115 }
116 $pArr[1] = implode(':', array($this->editorNo, $this->sys_language_content));
117 $pArr[2] = $this->RTEtsConfigParams;
118 if ($this->act == 'dragdrop' || $this->act == 'plain') {
119 $this->allowedFileTypes = explode(',', self::PLAIN_MODE_IMAGE_FILE_EXTENSIONS);
120 }
121 $pArr[3] = implode(',', $this->allowedFileTypes);
122 $this->bparams = implode('|', $pArr);
123 }
124
125 /**
126 * Initialize document template object
127 *
128 * @return void
129 */
130 protected function initDocumentTemplate() {
131 parent::initDocumentTemplate();
132
133 $this->doc->bodyTagId = 'typo3-browse-links-php';
134 $this->doc->bodyTagAdditions = $this->getBodyTagAdditions();
135
136 $this->doc->JScode .= $this->doc->wrapScriptTags('
137 Tree.ajaxID = "SC_alt_file_navframe::expandCollapse";
138 ');
139 $this->doc->getPageRenderer()->addCssFile($this->doc->backPath . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('t3skin') . 'rtehtmlarea/htmlarea.css');
140 $this->doc->getContextMenuCode();
141 }
142
143 /**
144 * Provide the additional parameters to be included in the template body tag
145 *
146 * @return string the body tag additions
147 */
148 public function getBodyTagAdditions() {
149 return 'onload="initEventListeners();"';
150 }
151
152 /**
153 * Get the path to the folder where RTE images are stored
154 *
155 * @return string the path to the folder where RTE images are stored
156 * @deprecated since 6.2, will be removed in two versions
157 */
158 protected function getRTEImageStorageDir() {
159 GeneralUtility::logDeprecatedFunction();
160 return $this->imgPath ?: $GLOBALS['TYPO3_CONF_VARS']['BE']['RTE_imageStorageDir'];
161 }
162
163 /**
164 * Insert the image in the editing area
165 *
166 * @return void
167 */
168 protected function insertImage() {
169 $table = htmlspecialchars(GeneralUtility::_GP('table'));
170 $uid = (int) GeneralUtility::_GP('uid');
171 if (GeneralUtility::_GP('insertImage') && $uid) {
172 /** @var $fileObject Resource\File */
173 $fileObject = Resource\ResourceFactory::getInstance()->getFileObject($uid);
174 // Get default values for alt and title attributes from file properties
175 $altText = $fileObject->getProperty('alternative');
176 $titleText = $fileObject->getProperty('title');
177 switch ($this->act) {
178 case 'magic':
179 $this->insertMagicImage($fileObject, $altText, $titleText, 'data-htmlarea-file-uid="' . $uid . '" data-htmlarea-file-table="' . $table . '"');
180 die;
181 break;
182 case 'plain':
183 $this->insertPlainImage($fileObject, $altText, $titleText, 'data-htmlarea-file-uid="' . $uid . '" data-htmlarea-file-table="' . $table . '"');
184 die;
185 break;
186 default:
187 // Call hook
188 foreach ($this->hookObjects as $hookObject) {
189 if (method_exists($hookObject, 'insertElement')) {
190 $hookObject->insertElement($this->act);
191 }
192 }
193 }
194 }
195 }
196
197 /**
198 * Insert a magic image
199 *
200 * @param Resource\File $fileObject: the image file
201 * @param string $altText: text for the alt attribute of the image
202 * @param string $titleText: text for the title attribute of the image
203 * @param string $additionalParams: text representing more HTML attributes to be added on the img tag
204 * @return void
205 */
206 public function insertMagicImage(Resource\File $fileObject, $altText = '', $titleText = '', $additionalParams = '') {
207 // Create the magic image service
208 /** @var $magicImageService Resource\Service\MagicImageService */
209 $magicImageService = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Service\\MagicImageService');
210 $magicImageService->setMagicImageMaximumDimensions($this->thisConfig);
211 // Create the magic image
212 $imageConfiguration = array(
213 'width' => GeneralUtility::_GP('cWidth'),
214 'height' => GeneralUtility::_GP('cHeight')
215 );
216 $magicImage = $magicImageService->createMagicImage($fileObject, $imageConfiguration);
217 $imageUrl = $magicImage->getPublicUrl();
218 // If file is local, make the url absolute
219 if (substr($imageUrl, 0, 4) !== 'http') {
220 $imageUrl = $this->siteURL . $imageUrl;
221 }
222 // Insert the magic image
223 $this->imageInsertJS($imageUrl, $magicImage->getProperty('width'), $magicImage->getProperty('height'), $altText, $titleText, $additionalParams);
224 }
225
226 /**
227 * Insert a plain image
228 *
229 * @param \TYPO3\CMS\Core\Resource\File $fileObject: the image file
230 * @param string $altText: text for the alt attribute of the image
231 * @param string $titleText: text for the title attribute of the image
232 * @param string $additionalParams: text representing more HTML attributes to be added on the img tag
233 * @return void
234 */
235 public function insertPlainImage(Resource\File $fileObject, $altText = '', $titleText = '', $additionalParams = '') {
236 $width = $fileObject->getProperty('width');
237 $height = $fileObject->getProperty('height');
238 if (!$width || !$height) {
239 $filePath = $fileObject->getForLocalProcessing(FALSE);
240 $imageInfo = @getimagesize($filePath);
241 $width = $imageInfo[0];
242 $height = $imageInfo[1];
243 }
244 $imageUrl = $fileObject->getPublicUrl();
245 // If file is local, make the url absolute
246 if (substr($imageUrl, 0, 4) !== 'http') {
247 $imageUrl = $this->siteURL . $imageUrl;
248 }
249 $this->imageInsertJS($imageUrl, $width, $height, $altText, $titleText, $additionalParams);
250 }
251
252 /**
253 * Echo the HTML page and JS that will insert the image
254 *
255 * @param string $url: the url of the image
256 * @param integer $width: the width of the image
257 * @param integer $height: the height of the image
258 * @param string $altText: text for the alt attribute of the image
259 * @param string $titleText: text for the title attribute of the image
260 * @param string $additionalParams: text representing more html attributes to be added on the img tag
261 * @return void
262 */
263 protected function imageInsertJS($url, $width, $height, $altText = '', $titleText = '', $additionalParams = '') {
264 echo '
265 <!DOCTYPE html>
266 <html>
267 <head>
268 <title>Untitled</title>
269 <script type="text/javascript">
270 /*<![CDATA[*/
271 var plugin = window.parent.RTEarea[' . GeneralUtility::quoteJSvalue($this->editorNo) . '].editor.getPlugin("TYPO3Image");
272 function insertImage(file,width,height,alt,title,additionalParams) {
273 plugin.insertImage(\'<img src="\'+file+\'" width="\'+parseInt(width)+\'" height="\'+parseInt(height)+\'"\'' . ($this->defaultClass ? '+\' class="' . $this->defaultClass . '"\'' : '') . '+(alt?\' alt="\'+alt+\'"\':\'\')+(title?\' title="\'+title+\'"\':\'\')+(additionalParams?\' \'+additionalParams:\'\')+\' />\');
274 }
275 /*]]>*/
276 </script>
277 </head>
278 <body>
279 <script type="text/javascript">
280 /*<![CDATA[*/
281 insertImage(' . GeneralUtility::quoteJSvalue($url, 1) . ',' . $width . ',' . $height . ',' . GeneralUtility::quoteJSvalue($altText, 1) . ',' . GeneralUtility::quoteJSvalue($titleText, 1) . ',' . GeneralUtility::quoteJSvalue($additionalParams, 1) . ');
282 /*]]>*/
283 </script>
284 </body>
285 </html>';
286 }
287
288 /**
289 * Generate JS code to be used on the image insert/modify dialogue
290 *
291 * @param string $act: the action to be performed
292 * @param string $editorNo: the number of the RTE instance on the page
293 * @param string $sys_language_content: the language of the content element
294 * @return string the generated JS code
295 * @todo Define visibility
296 */
297 public function getJSCode($act, $editorNo, $sys_language_content) {
298 $removedProperties = array();
299 if (is_array($this->buttonConfig['properties.'])) {
300 if ($this->buttonConfig['properties.']['removeItems']) {
301 $removedProperties = GeneralUtility::trimExplode(',', $this->buttonConfig['properties.']['removeItems'], TRUE);
302 }
303 }
304 if ($this->buttonConfig['properties.']['class.']['allowedClasses']) {
305 $classesImageArray = GeneralUtility::trimExplode(',', $this->buttonConfig['properties.']['class.']['allowedClasses'], TRUE);
306 $classesImageJSOptions = '<option value=""></option>';
307 foreach ($classesImageArray as $class) {
308 $classesImageJSOptions .= '<option value="' . $class . '">' . $class . '</option>';
309 }
310 }
311 $lockPlainWidth = 'false';
312 $lockPlainHeight = 'false';
313 if (is_array($this->thisConfig['proc.']) && $this->thisConfig['proc.']['plainImageMode']) {
314 $plainImageMode = $this->thisConfig['proc.']['plainImageMode'];
315 $lockPlainWidth = $plainImageMode == 'lockDimensions' ? 'true' : 'false';
316 $lockPlainHeight = $lockPlainWidth || $plainImageMode == 'lockRatio' || $plainImageMode == 'lockRatioWhenSmaller' ? 'true' : 'false';
317 }
318 $JScode = '
319 var plugin = window.parent.RTEarea[' . GeneralUtility::quoteJSvalue($editorNo) . '].editor.getPlugin("TYPO3Image");
320 var HTMLArea = window.parent.HTMLArea;
321
322 HTMLArea.TYPO3Image.insertElement = function (table, uid, type, filename, filePath, fileExt, fileIcon) {
323 return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'editorNo=') . ' + ' . GeneralUtility::quoteJSvalue($editorNo) . ' + \'&insertImage=\' + filePath + \'&table=\' + table + \'&uid=\' + uid + \'&type=\' + type + \'bparams=\' + ' . GeneralUtility::quoteJSvalue($this->bparams) . ');
324 }
325 function insertElement(table, uid, type, fileName, filePath, fileExt, fileIcon, action, close) {
326 return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'editorNo=') . ' + ' . GeneralUtility::quoteJSvalue($editorNo) . ' + \'&insertImage=\' + filePath + \'&table=\' + table + \'&uid=\' + uid + \'&type=\' + type + \'bparams=\' + ' . GeneralUtility::quoteJSvalue($this->bparams) . ');
327 }
328 function initEventListeners() {
329 if (Ext.isWebKit) {
330 Ext.EventManager.addListener(window.document.body, "dragend", plugin.onDrop, plugin, { single: true });
331 }
332 }
333 function jumpToUrl(URL,anchor) {
334 var add_act = URL.indexOf("act=")==-1 ? "&act=' . $act . '" : "";
335 var add_editorNo = URL.indexOf("editorNo=")==-1 ? "&editorNo=' . $editorNo . '" : "";
336 var add_sys_language_content = URL.indexOf("sys_language_content=")==-1 ? "&sys_language_content=' . $sys_language_content . '" : "";
337 var RTEtsConfigParams = "&RTEtsConfigParams=' . rawurlencode($this->RTEtsConfigParams) . '";
338
339 var cur_width = selectedImageRef ? "&cWidth="+selectedImageRef.style.width : "";
340 var cur_height = selectedImageRef ? "&cHeight="+selectedImageRef.style.height : "";
341
342 var theLocation = URL+add_act+add_editorNo+add_sys_language_content+RTEtsConfigParams+cur_width+cur_height+(typeof(anchor)=="string"?anchor:"");
343 window.location.href = theLocation;
344 return false;
345 }
346 function insertImage(file,width,height) {
347 plugin.insertImage(\'<img src="\'+file+\'"' . ($this->defaultClass ? ' class="' . $this->defaultClass . '"' : '') . ' width="\'+parseInt(width)+\'" height="\'+parseInt(height)+\'" />\');
348 }
349 function launchView(url) {
350 var thePreviewWindow="";
351 thePreviewWindow = window.open("' . GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir . 'show_item.php?table="+url,"ShowItem","height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0");
352 if (thePreviewWindow && thePreviewWindow.focus) {
353 thePreviewWindow.focus();
354 }
355 }
356 function getCurrentImageRef() {
357 if (plugin.image) {
358 return plugin.image;
359 } else {
360 return null;
361 }
362 }
363 function printCurrentImageOptions() {
364 var classesImage = ' . ($this->buttonConfig['properties.']['class.']['allowedClasses'] || $this->thisConfig['classesImage'] ? 'true' : 'false') . ';
365 if (classesImage) var styleSelector=\'<select id="iClass" name="iClass" style="width:140px;">' . $classesImageJSOptions . '</select>\';
366 var floatSelector=\'<select id="iFloat" name="iFloat"><option value="">' . $GLOBALS['LANG']->getLL('notSet') . '</option><option value="none">' . $GLOBALS['LANG']->getLL('nonFloating') . '</option><option value="left">' . $GLOBALS['LANG']->getLL('left') . '</option><option value="right">' . $GLOBALS['LANG']->getLL('right') . '</option></select>\';
367 if (plugin.getButton("Language")) {
368 var languageSelector = \'<select id="iLang" name="iLang">\';
369 plugin.getButton("Language").getStore().each(function (record) {
370 languageSelector +=\'<option value="\' + record.get("value") + \'">\' + record.get("text") + \'</option>\';
371 });
372 languageSelector += \'</select>\';
373 }
374 var sz="";
375 sz+=\'<form action="" name="imageData"><table class="htmlarea-window-table">\';
376 ' . (in_array('class', $removedProperties) ? '' : '
377 if(classesImage) {
378 sz+=\'<tr><td><label for="iClass">' . $GLOBALS['LANG']->getLL('class') . ': </label></td><td>\'+styleSelector+\'</td></tr>\';
379 }') . (in_array('width', $removedProperties) ? '' : '
380 if (!(selectedImageRef && selectedImageRef.src.indexOf("RTEmagic") == -1 && ' . $lockPlainWidth . ')) {
381 sz+=\'<tr><td><label for="iWidth">' . $GLOBALS['LANG']->getLL('width') . ': </label></td><td><input type="text" id="iWidth" name="iWidth" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . ' /></td></tr>\';
382 }') . (in_array('height', $removedProperties) ? '' : '
383 if (!(selectedImageRef && selectedImageRef.src.indexOf("RTEmagic") == -1 && ' . $lockPlainHeight . ')) {
384 sz+=\'<tr><td><label for="iHeight">' . $GLOBALS['LANG']->getLL('height') . ': </label></td><td><input type="text" id="iHeight" name="iHeight" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . ' /></td></tr>\';
385 }') . (in_array('border', $removedProperties) ? '' : '
386 sz+=\'<tr><td><label for="iBorder">' . $GLOBALS['LANG']->getLL('border') . ': </label></td><td><input type="checkbox" id="iBorder" name="iBorder" value="1" /></td></tr>\';') . (in_array('float', $removedProperties) ? '' : '
387 sz+=\'<tr><td><label for="iFloat">' . $GLOBALS['LANG']->getLL('float') . ': </label></td><td>\'+floatSelector+\'</td></tr>\';') . (in_array('paddingTop', $removedProperties) ? '' : '
388 sz+=\'<tr><td><label for="iPaddingTop">' . $GLOBALS['LANG']->getLL('padding_top') . ': </label></td><td><input type="text" id="iPaddingTop" name="iPaddingTop" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . '></td></tr>\';') . (in_array('paddingRight', $removedProperties) ? '' : '
389 sz+=\'<tr><td><label for="iPaddingRight">' . $GLOBALS['LANG']->getLL('padding_right') . ': </label></td><td><input type="text" id="iPaddingRight" name="iPaddingRight" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . ' /></td></tr>\';') . (in_array('paddingBottom', $removedProperties) ? '' : '
390 sz+=\'<tr><td><label for="iPaddingBottom">' . $GLOBALS['LANG']->getLL('padding_bottom') . ': </label></td><td><input type="text" id="iPaddingBottom" name="iPaddingBottom" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . ' /></td></tr>\';') . (in_array('paddingLeft', $removedProperties) ? '' : '
391 sz+=\'<tr><td><label for="iPaddingLeft">' . $GLOBALS['LANG']->getLL('padding_left') . ': </label></td><td><input type="text" id="iPaddingLeft" name="iPaddingLeft" value=""' . $GLOBALS['TBE_TEMPLATE']->formWidth(4) . ' /></td></tr>\';') . (in_array('title', $removedProperties) ? '' : '
392 sz+=\'<tr><td><label for="iTitle">' . $GLOBALS['LANG']->getLL('title') . ': </label></td><td><input type="text" id="iTitle" name="iTitle"' . $GLOBALS['TBE_TEMPLATE']->formWidth(20) . ' /></td></tr>\';') . (in_array('alt', $removedProperties) ? '' : '
393 sz+=\'<tr><td><label for="iAlt">' . $GLOBALS['LANG']->getLL('alt') . ': </label></td><td><input type="text" id="iAlt" name="iAlt"' . $GLOBALS['TBE_TEMPLATE']->formWidth(20) . ' /></td></tr>\';') . (in_array('lang', $removedProperties) ? '' : '
394 if (plugin.getButton("Language")) {
395 sz+=\'<tr><td><label for="iLang">\' + plugin.editor.getPlugin("Language").localize(\'Language-Tooltip\') + \': </label></td><td>\' + languageSelector + \'</td></tr>\';
396 }') . (in_array('clickenlarge', $removedProperties) || in_array('data-htmlarea-clickenlarge', $removedProperties) ? '' : '
397 sz+=\'<tr><td><label for="iClickEnlarge">' . $GLOBALS['LANG']->sL('LLL:EXT:cms/locallang_ttc.xlf:image_zoom', TRUE) . ' </label></td><td><input type="checkbox" name="iClickEnlarge" id="iClickEnlarge" value="0" /></td></tr>\';') . '
398 sz+=\'<tr><td></td><td><input type="submit" value="' . $GLOBALS['LANG']->getLL('update') . '" onClick="return setImageProperties();"></td></tr>\';
399 sz+=\'</table></form>\';
400 return sz;
401 }
402 function setImageProperties() {
403 var classesImage = ' . ($this->buttonConfig['properties.']['class.']['allowedClasses'] || $this->thisConfig['classesImage'] ? 'true' : 'false') . ';
404 if (selectedImageRef) {
405 if (document.imageData.iWidth) {
406 if (document.imageData.iWidth.value && parseInt(document.imageData.iWidth.value)) {
407 selectedImageRef.style.width = "";
408 selectedImageRef.width = parseInt(document.imageData.iWidth.value);
409 }
410 }
411 if (document.imageData.iHeight) {
412 if (document.imageData.iHeight.value && parseInt(document.imageData.iHeight.value)) {
413 selectedImageRef.style.height = "";
414 selectedImageRef.height = parseInt(document.imageData.iHeight.value);
415 }
416 }
417 if (document.imageData.iPaddingTop) {
418 if (document.imageData.iPaddingTop.value != "" && !isNaN(parseInt(document.imageData.iPaddingTop.value))) {
419 selectedImageRef.style.paddingTop = parseInt(document.imageData.iPaddingTop.value) + "px";
420 } else {
421 selectedImageRef.style.paddingTop = "";
422 }
423 }
424 if (document.imageData.iPaddingRight) {
425 if (document.imageData.iPaddingRight.value != "" && !isNaN(parseInt(document.imageData.iPaddingRight.value))) {
426 selectedImageRef.style.paddingRight = parseInt(document.imageData.iPaddingRight.value) + "px";
427 } else {
428 selectedImageRef.style.paddingRight = "";
429 }
430 }
431 if (document.imageData.iPaddingBottom) {
432 if (document.imageData.iPaddingBottom.value != "" && !isNaN(parseInt(document.imageData.iPaddingBottom.value))) {
433 selectedImageRef.style.paddingBottom = parseInt(document.imageData.iPaddingBottom.value) + "px";
434 } else {
435 selectedImageRef.style.paddingBottom = "";
436 }
437 }
438 if (document.imageData.iPaddingLeft) {
439 if (document.imageData.iPaddingLeft.value != "" && !isNaN(parseInt(document.imageData.iPaddingLeft.value))) {
440 selectedImageRef.style.paddingLeft = parseInt(document.imageData.iPaddingLeft.value) + "px";
441 } else {
442 selectedImageRef.style.paddingLeft = "";
443 }
444 }
445 if (document.imageData.iTitle) {
446 selectedImageRef.title=document.imageData.iTitle.value;
447 }
448 if (document.imageData.iAlt) {
449 selectedImageRef.alt=document.imageData.iAlt.value;
450 }
451 if (document.imageData.iBorder) {
452 selectedImageRef.style.borderStyle = "";
453 selectedImageRef.style.borderWidth = "";
454 selectedImageRef.style.border = ""; // this statement ignored by Mozilla 1.3.1
455 selectedImageRef.style.borderTopStyle = "";
456 selectedImageRef.style.borderRightStyle = "";
457 selectedImageRef.style.borderBottomStyle = "";
458 selectedImageRef.style.borderLeftStyle = "";
459 selectedImageRef.style.borderTopWidth = "";
460 selectedImageRef.style.borderRightWidth = "";
461 selectedImageRef.style.borderBottomWidth = "";
462 selectedImageRef.style.borderLeftWidth = "";
463 if(document.imageData.iBorder.checked) {
464 selectedImageRef.style.borderStyle = "solid";
465 selectedImageRef.style.borderWidth = "thin";
466 }
467 selectedImageRef.removeAttribute("border");
468 }
469 if (document.imageData.iFloat) {
470 var iFloat = document.imageData.iFloat.options[document.imageData.iFloat.selectedIndex].value;
471 if (document.all) {
472 selectedImageRef.style.styleFloat = iFloat ? iFloat : "";
473 } else {
474 selectedImageRef.style.cssFloat = iFloat ? iFloat : "";
475 }
476 }
477 if (classesImage && document.imageData.iClass) {
478 var iClass;
479 if (document.imageData.iClass.options.length > 0) {
480 iClass = document.imageData.iClass.options[document.imageData.iClass.selectedIndex].value;
481 }
482 if (iClass || (selectedImageRef.attributes["class"] && selectedImageRef.attributes["class"].value)) {
483 selectedImageRef.className = iClass;
484 } else {
485 selectedImageRef.className = "";
486 }
487 }
488 if (document.imageData.iLang) {
489 var iLang = document.imageData.iLang.options[document.imageData.iLang.selectedIndex].value;
490 var languageObject = plugin.editor.getPlugin("Language");
491 if (iLang || languageObject.getLanguageAttribute(selectedImageRef)) {
492 languageObject.setLanguageAttributes(selectedImageRef, iLang);
493 } else {
494 languageObject.setLanguageAttributes(selectedImageRef, "none");
495 }
496 }
497 if (document.imageData.iClickEnlarge) {
498 if (document.imageData.iClickEnlarge.checked) {
499 selectedImageRef.setAttribute("data-htmlarea-clickenlarge","1");
500 } else {
501 selectedImageRef.removeAttribute("data-htmlarea-clickenlarge");
502 selectedImageRef.removeAttribute("clickenlarge");
503 }
504 }
505 plugin.close();
506 }
507 return false;
508 }
509 function insertImagePropertiesInForm() {
510 var classesImage = ' . ($this->buttonConfig['properties.']['class.']['allowedClasses'] || $this->thisConfig['classesImage'] ? 'true' : 'false') . ';
511 if (selectedImageRef) {
512 var styleWidth, styleHeight, padding;
513 if (document.imageData.iWidth) {
514 styleWidth = selectedImageRef.style.width ? selectedImageRef.style.width : selectedImageRef.width;
515 styleWidth = parseInt(styleWidth);
516 if (!(isNaN(styleWidth) || styleWidth == 0)) {
517 document.imageData.iWidth.value = styleWidth;
518 }
519 }
520 if (document.imageData.iHeight) {
521 styleHeight = selectedImageRef.style.height ? selectedImageRef.style.height : selectedImageRef.height;
522 styleHeight = parseInt(styleHeight);
523 if (!(isNaN(styleHeight) || styleHeight == 0)) {
524 document.imageData.iHeight.value = styleHeight;
525 }
526 }
527 if (document.imageData.iPaddingTop) {
528 var padding = selectedImageRef.style.paddingTop ? selectedImageRef.style.paddingTop : selectedImageRef.vspace;
529 var padding = parseInt(padding);
530 if (isNaN(padding) || padding <= 0) { padding = ""; }
531 document.imageData.iPaddingTop.value = padding;
532 }
533 if (document.imageData.iPaddingRight) {
534 padding = selectedImageRef.style.paddingRight ? selectedImageRef.style.paddingRight : selectedImageRef.hspace;
535 var padding = parseInt(padding);
536 if (isNaN(padding) || padding <= 0) { padding = ""; }
537 document.imageData.iPaddingRight.value = padding;
538 }
539 if (document.imageData.iPaddingBottom) {
540 var padding = selectedImageRef.style.paddingBottom ? selectedImageRef.style.paddingBottom : selectedImageRef.vspace;
541 var padding = parseInt(padding);
542 if (isNaN(padding) || padding <= 0) { padding = ""; }
543 document.imageData.iPaddingBottom.value = padding;
544 }
545 if (document.imageData.iPaddingLeft) {
546 var padding = selectedImageRef.style.paddingLeft ? selectedImageRef.style.paddingLeft : selectedImageRef.hspace;
547 var padding = parseInt(padding);
548 if (isNaN(padding) || padding <= 0) { padding = ""; }
549 document.imageData.iPaddingLeft.value = padding;
550 }
551 if (document.imageData.iTitle) {
552 document.imageData.iTitle.value = selectedImageRef.title;
553 }
554 if (document.imageData.iAlt) {
555 document.imageData.iAlt.value = selectedImageRef.alt;
556 }
557 if (document.imageData.iBorder) {
558 if((selectedImageRef.style.borderStyle && selectedImageRef.style.borderStyle != "none" && selectedImageRef.style.borderStyle != "none none none none") || selectedImageRef.border) {
559 document.imageData.iBorder.checked = 1;
560 }
561 }
562 if (document.imageData.iFloat) {
563 var fObj=document.imageData.iFloat;
564 var value = (selectedImageRef.style.cssFloat ? selectedImageRef.style.cssFloat : selectedImageRef.style.styleFloat);
565 var l=fObj.length;
566 for (var a=0;a<l;a++) {
567 if (fObj.options[a].value == value) {
568 fObj.selectedIndex = a;
569 }
570 }
571 }
572 if (classesImage && document.imageData.iClass) {
573 var fObj=document.imageData.iClass;
574 var value=selectedImageRef.className;
575 var l=fObj.length;
576 for (var a=0;a < l; a++) {
577 if (fObj.options[a].value == value) {
578 fObj.selectedIndex = a;
579 }
580 }
581 }
582 if (document.imageData.iLang) {
583 var fObj=document.imageData.iLang;
584 var value=plugin.editor.getPlugin("Language").getLanguageAttribute(selectedImageRef);
585 for (var i = 0, n = fObj.length; i < n; i++) {
586 if (fObj.options[i].value == value) {
587 fObj.selectedIndex = i;
588 if (i) {
589 fObj.options[0].text = plugin.editor.getPlugin("Language").localize("Remove language mark");
590 }
591 }
592 }
593 }
594 if (document.imageData.iClickEnlarge) {
595 if (selectedImageRef.getAttribute("data-htmlarea-clickenlarge") == "1" || selectedImageRef.getAttribute("clickenlarge") == "1") {
596 document.imageData.iClickEnlarge.checked = 1;
597 } else {
598 document.imageData.iClickEnlarge.checked = 0;
599 }
600 }
601 return false;
602 }
603 }
604
605 var selectedImageRef = getCurrentImageRef();';
606 // Setting this to a reference to the image object.
607 return $JScode;
608 }
609
610 /**
611 * Session data for this class can be set from outside with this method.
612 * Call after init()
613 *
614 * @param array $data Session data array
615 * @return array Session data and boolean which indicates that data needs to be stored in session because it's changed
616 * @todo Define visibility
617 */
618 public function processSessionData($data) {
619 $store = FALSE;
620 if ($this->act != 'image') {
621 if (isset($this->act)) {
622 $data['act'] = $this->act;
623 $store = TRUE;
624 } else {
625 $this->act = $data['act'];
626 }
627 }
628 if (isset($this->expandFolder)) {
629 $data['expandFolder'] = $this->expandFolder;
630 $store = TRUE;
631 } else {
632 $this->expandFolder = $data['expandFolder'];
633 }
634 return array($data, $store);
635 }
636
637 /**
638 * @return string
639 * @todo Define visibility
640 */
641 public function main_rte() {
642 // Starting content:
643 $this->content = $this->doc->startPage($GLOBALS['LANG']->getLL('Insert Image', TRUE));
644 // Making menu in top:
645 $menuDef = array();
646 if (in_array('image', $this->allowedItems) && ($this->act === 'image' || GeneralUtility::_GP('cWidth'))) {
647 $menuDef['image']['isActive'] = FALSE;
648 $menuDef['image']['label'] = $GLOBALS['LANG']->getLL('currentImage', TRUE);
649 $menuDef['image']['url'] = '#';
650 $menuDef['image']['addParams'] = 'onClick="jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=image&bparams=' . $this->bparams) . ');return false;"';
651 }
652 if (in_array('magic', $this->allowedItems)) {
653 $menuDef['magic']['isActive'] = FALSE;
654 $menuDef['magic']['label'] = $GLOBALS['LANG']->getLL('magicImage', TRUE);
655 $menuDef['magic']['url'] = '#';
656 $menuDef['magic']['addParams'] = 'onClick="jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=magic&bparams=' . $this->bparams) . ');return false;"';
657 }
658 if (in_array('plain', $this->allowedItems)) {
659 $menuDef['plain']['isActive'] = FALSE;
660 $menuDef['plain']['label'] = $GLOBALS['LANG']->getLL('plainImage', TRUE);
661 $menuDef['plain']['url'] = '#';
662 $menuDef['plain']['addParams'] = 'onClick="jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=plain&bparams=' . $this->bparams) . ');return false;"';
663 }
664 if (in_array('dragdrop', $this->allowedItems)) {
665 $menuDef['dragdrop']['isActive'] = FALSE;
666 $menuDef['dragdrop']['label'] = $GLOBALS['LANG']->getLL('dragDropImage', TRUE);
667 $menuDef['dragdrop']['url'] = '#';
668 $menuDef['dragdrop']['addParams'] = 'onClick="jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . 'act=dragdrop&bparams=' . $this->bparams) . ');return false;"';
669 }
670 // Call hook for extra options
671 foreach ($this->hookObjects as $hookObject) {
672 $menuDef = $hookObject->modifyMenuDefinition($menuDef);
673 }
674 // Order the menu items as specified in Page TSconfig
675 $menuDef = $this->orderMenuDefinition($menuDef);
676 // Set active menu item
677 reset($menuDef);
678 if ($this->act === FALSE || !in_array($this->act, $this->allowedItems)) {
679 $this->act = key($menuDef);
680 }
681 $menuDef[$this->act]['isActive'] = TRUE;
682 $this->content .= $this->doc->getTabMenuRaw($menuDef);
683 switch ($this->act) {
684 case 'image':
685 $JScode = '
686 document.write(printCurrentImageOptions());
687 insertImagePropertiesInForm();';
688 $this->content .= '<br />' . $this->doc->wrapScriptTags($JScode);
689 break;
690 case 'plain':
691
692 case 'magic':
693 // Create folder tree:
694 $foldertree = GeneralUtility::makeInstance('TYPO3\\CMS\\Rtehtmlarea\\FolderTree');
695 $foldertree->thisScript = $this->thisScript;
696 $tree = $foldertree->getBrowsableTree();
697 // Get currently selected folder
698 if (!$this->curUrlInfo['value'] || $this->curUrlInfo['act'] != $this->act) {
699 $cmpPath = '';
700 } else {
701 $cmpPath = $this->curUrlInfo['value'];
702 if (!isset($this->expandFolder)) {
703 $this->expandFolder = $cmpPath;
704 }
705 }
706 // Get the selected folder
707 $selectedFolder = FALSE;
708 if ($this->expandFolder) {
709 $fileOrFolderObject = NULL;
710 try {
711 $fileOrFolderObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
712 } catch (\Exception $e) {
713 // No path is selected
714 }
715 if ($fileOrFolderObject instanceof \TYPO3\CMS\Core\Resource\Folder) {
716 // it's a folder
717 $selectedFolder = $fileOrFolderObject;
718 } elseif ($fileOrFolderObject instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
719 // it's a file
720 try {
721 $selectedFolder = $fileOrFolderObject->getParentFolder();
722 } catch (\Exception $e) {
723 // Accessing the parent folder failed for some reason. e.g. permissions
724 }
725 }
726 }
727 // If no folder is selected, get the user's default upload folder
728 if (!$selectedFolder) {
729 try {
730 $selectedFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
731 } catch (\Exception $e) {
732 // The configured default user folder does not exist
733 }
734 }
735 // Build the file upload and folder creation form
736 $uploadForm = '';
737 $createFolder = '';
738 if ($selectedFolder) {
739 $uploadForm = $this->uploadForm($selectedFolder);
740 $createFolder = $this->createFolder($selectedFolder);
741 }
742 // Insert the upload form on top, if so configured
743 if ($GLOBALS['BE_USER']->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
744 $this->content .= $uploadForm;
745 }
746 // Render the filelist if there is a folder selected
747 $files = '';
748 if ($selectedFolder) {
749 $files = $this->TBE_expandFolder($selectedFolder, $this->act === 'plain' ? self::PLAIN_MODE_IMAGE_FILE_EXTENSIONS : $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $GLOBALS['BE_USER']->getTSConfigVal('options.noThumbsInRTEimageSelect'));
750 }
751 // Setup filelist indexed elements:
752 $this->doc->JScode .= $this->doc->wrapScriptTags('BrowseLinks.addElements(' . json_encode($this->elements) . ');');
753 // Wrap tree
754 $this->content .= '
755
756 <!--
757 Wrapper table for folder tree / file/folder list:
758 -->
759 <table border="0" cellpadding="0" cellspacing="0" id="typo3-linkFiles">
760 <tr>
761 <td class="c-wCell" valign="top">' . $this->barheader(($GLOBALS['LANG']->getLL('folderTree') . ':')) . $tree . '</td>
762 <td class="c-wCell" valign="top">' . $files . '</td>
763 </tr>
764 </table>
765 ';
766 // Add help message
767 $helpMessage = $this->getHelpMessage($this->act);
768 if ($helpMessage) {
769 $this->content .= $this->getMsgBox($helpMessage);
770 }
771 // Adding create folder + upload form if applicable
772 if (!$GLOBALS['BE_USER']->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
773 $this->content .= $uploadForm;
774 }
775 $this->content .= $createFolder;
776 $this->content .= '<br />';
777 break;
778 case 'dragdrop':
779 $foldertree = GeneralUtility::makeInstance('TBE_FolderTree');
780 $foldertree->thisScript = $this->thisScript;
781 $foldertree->ext_noTempRecyclerDirs = TRUE;
782 $tree = $foldertree->getBrowsableTree();
783 // Get currently selected folder
784 if (!$this->curUrlInfo['value'] || $this->curUrlInfo['act'] != $this->act) {
785 $cmpPath = '';
786 } else {
787 $cmpPath = $this->curUrlInfo['value'];
788 if (!isset($this->expandFolder)) {
789 $this->expandFolder = $cmpPath;
790 }
791 }
792 $selectedFolder = FALSE;
793 if ($this->expandFolder) {
794 try {
795 $selectedFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($this->expandFolder);
796 } catch (\Exception $e) {
797 }
798 }
799 // Render the filelist if there is a folder selected
800 $files = '';
801 if ($selectedFolder) {
802 $files = $this->TBE_dragNDrop($selectedFolder, implode(',', $this->allowedFileTypes));
803 }
804 // Wrap tree
805 $this->content .= '<table border="0" cellpadding="0" cellspacing="0">
806 <tr>
807 <td style="vertical-align: top;">' . $this->barheader(($GLOBALS['LANG']->getLL('folderTree') . ':')) . $tree . '</td>
808 <td>&nbsp;</td>
809 <td style="vertical-align: top;">' . $files . '</td>
810 </tr>
811 </table>';
812 break;
813 default:
814 // Call hook
815 foreach ($this->hookObjects as $hookObject) {
816 $this->content .= $hookObject->getTab($this->act);
817 }
818 }
819 $this->content .= $this->doc->endPage();
820
821 // unset the default jumpToUrl() function
822 unset($this->doc->JScodeArray['jumpToUrl']);
823
824 $this->doc->JScodeArray['rtehtmlarea'] = $this->getJSCode($this->act, $this->editorNo, $this->sys_language_content);
825 $this->content = $this->doc->insertStylesAndJS($this->content);
826 return $this->content;
827 }
828
829 /**
830 * Initializes the configuration variables
831 *
832 * @return void
833 */
834 public function initConfiguration() {
835 $this->thisConfig = $this->getRTEConfig();
836 $this->buttonConfig = $this->getButtonConfig();
837 $this->imgPath = $this->getImgPath();
838 $this->defaultClass = $this->getDefaultClass();
839 $this->setMaximumPlainImageDimensions();
840 }
841
842 /**
843 * Get the path of the image to be inserted or modified
844 *
845 * @return string path to the image
846 */
847 protected function getImgPath() {
848 $RTEtsConfigParts = explode(':', $this->RTEtsConfigParams);
849 return $RTEtsConfigParts[6];
850 }
851
852 /**
853 * Get the configuration of the image button
854 *
855 * @return array the configuration array of the image button
856 */
857 protected function getButtonConfig() {
858 return is_array($this->thisConfig['buttons.']) && is_array($this->thisConfig['buttons.']['image.']) ? $this->thisConfig['buttons.']['image.'] : array();
859 }
860
861 /**
862 * Get the allowed items or tabs
863 *
864 * @param string $items: initial list of possible items
865 * @return array the allowed items
866 */
867 public function getAllowedItems($items) {
868 $allowedItems = explode(',', $items);
869 $clientInfo = GeneralUtility::clientInfo();
870 if ($clientInfo['BROWSER'] !== 'opera') {
871 $allowedItems[] = 'dragdrop';
872 }
873 // Call hook for extra options
874 foreach ($this->hookObjects as $hookObject) {
875 $allowedItems = $hookObject->addAllowedItems($allowedItems);
876 }
877 // Remove tab "image" if there is no current image
878 if ($this->act !== 'image') {
879 $allowedItems = array_diff($allowedItems, array('image'));
880 }
881 // Remove options according to RTE configuration
882 if (is_array($this->buttonConfig['options.']) && $this->buttonConfig['options.']['removeItems']) {
883 $allowedItems = array_diff($allowedItems, GeneralUtility::trimExplode(',', $this->buttonConfig['options.']['removeItems'], TRUE));
884 }
885 return $allowedItems;
886 }
887
888 /**
889 * Order the definition of menu items according to configured order
890 *
891 * @param array $menuDefinition: definition of menu items
892 * @return array ordered menu definition
893 */
894 public function orderMenuDefinition($menuDefinition) {
895 $orderedMenuDefinition = array();
896 if (is_array($this->buttonConfig['options.']) && $this->buttonConfig['options.']['orderItems']) {
897 $orderItems = GeneralUtility::trimExplode(',', $this->buttonConfig['options.']['orderItems'], TRUE);
898 $orderItems = array_intersect($orderItems, $this->allowedItems);
899 foreach ($orderItems as $item) {
900 $orderedMenuDefinition[$item] = $menuDefinition[$item];
901 }
902 } else {
903 $orderedMenuDefinition = $menuDefinition;
904 }
905 return $orderedMenuDefinition;
906 }
907
908 /**
909 * Get the default image class
910 *
911 * @return string the default class, if any
912 */
913 protected function getDefaultClass() {
914 $defaultClass = '';
915 if (is_array($this->buttonConfig['properties.'])) {
916 if (is_array($this->buttonConfig['properties.']['class.']) && trim($this->buttonConfig['properties.']['class.']['default'])) {
917 $defaultClass = trim($this->buttonConfig['properties.']['class.']['default']);
918 }
919 }
920 return $defaultClass;
921 }
922
923 /**
924 * Set variables for maximum plain image dimensions
925 *
926 * @return void
927 */
928 protected function setMaximumPlainImageDimensions() {
929 if (is_array($this->buttonConfig['options.']) && is_array($this->buttonConfig['options.']['plain.'])) {
930 if ($this->buttonConfig['options.']['plain.']['maxWidth']) {
931 $this->plainMaxWidth = $this->buttonConfig['options.']['plain.']['maxWidth'];
932 }
933 if ($this->buttonConfig['options.']['plain.']['maxHeight']) {
934 $this->plainMaxHeight = $this->buttonConfig['options.']['plain.']['maxHeight'];
935 }
936 }
937 if (!$this->plainMaxWidth) {
938 $this->plainMaxWidth = 640;
939 }
940 if (!$this->plainMaxHeight) {
941 $this->plainMaxHeight = 680;
942 }
943 }
944
945 /**
946 * Get the help message to be displayed on a given tab
947 *
948 * @param string $act: the identifier of the tab
949 * @return string the text of the message
950 */
951 public function getHelpMessage($act) {
952 switch ($act) {
953 case 'plain':
954 return sprintf($GLOBALS['LANG']->getLL('plainImage_msg'), $this->plainMaxWidth, $this->plainMaxHeight);
955 break;
956 case 'magic':
957 return sprintf($GLOBALS['LANG']->getLL('magicImage_msg'));
958 break;
959 default:
960 return '';
961 }
962 }
963
964 /**
965 * Checks if the given file is selectable in the file list.
966 *
967 * In "plain" RTE mode only image files with a maximum width and height are selectable.
968 *
969 * @param \TYPO3\CMS\Core\Resource\FileInterface $file
970 * @param array $imgInfo Image dimensions from \TYPO3\CMS\Core\Imaging\GraphicalFunctions::getImageDimensions()
971 * @return bool TRUE if file is selectable.
972 */
973 protected function fileIsSelectableInFileList(\TYPO3\CMS\Core\Resource\FileInterface $file, array $imgInfo) {
974 return (
975 $this->act !== 'plain'
976 || (
977 GeneralUtility::inList(self::PLAIN_MODE_IMAGE_FILE_EXTENSIONS, strtolower($file->getExtension()))
978 && $imgInfo[0] <= $this->plainMaxWidth
979 && $imgInfo[1] <= $this->plainMaxHeight
980 )
981 );
982 }
983 }