0e4b4cedf82823805531d1279dba007bb80045c5
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / Wizard / ColorpickerController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller\Wizard;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ServerRequestInterface;
18 use Psr\Http\Message\ResponseInterface;
19 use TYPO3\CMS\Backend\Template\DocumentTemplate;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Page\PageRenderer;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Script Class for colorpicker wizard
26 */
27 class ColorpickerController extends AbstractWizardController
28 {
29 /**
30 * Wizard parameters, coming from FormEngine linking to the wizard.
31 *
32 * @var array
33 */
34 public $P;
35
36 /**
37 * Value of the current color picked.
38 *
39 * @var string
40 */
41 public $colorValue;
42
43 /**
44 * Serialized functions for changing the field...
45 * Necessary to call when the value is transferred to the FormEngine since the form might
46 * need to do internal processing. Otherwise the value is simply not be saved.
47 *
48 * @var string
49 */
50 public $fieldChangeFunc;
51
52 /**
53 * @var string
54 */
55 protected $fieldChangeFuncHash;
56
57 /**
58 * Form name (from opener script)
59 *
60 * @var string
61 */
62 public $fieldName;
63
64 /**
65 * Field name (from opener script)
66 *
67 * @var string
68 */
69 public $formName;
70
71 /**
72 * ID of element in opener script for which to set color.
73 *
74 * @var string
75 */
76 public $md5ID;
77
78 /**
79 * Internal: If FALSE, a frameset is rendered, if TRUE the content of the picker script.
80 *
81 * @var int
82 */
83 public $showPicker;
84
85 /**
86 * @var string
87 */
88 public $HTMLcolorList = 'aqua,black,blue,fuchsia,gray,green,lime,maroon,navy,olive,purple,red,silver,teal,yellow,white';
89
90 /**
91 * @var string
92 */
93 public $pickerImage = '';
94
95 /**
96 * Error message if image not found.
97 *
98 * @var string
99 */
100 public $imageError = '';
101
102 /**
103 * Document template object
104 *
105 * @var DocumentTemplate
106 */
107 public $doc;
108
109 /**
110 * @var string
111 */
112 public $content;
113
114 /**
115 * @var string
116 */
117 protected $exampleImg;
118
119 /**
120 * Constructor
121 */
122 public function __construct()
123 {
124 parent::__construct();
125 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_wizards.xlf');
126 $GLOBALS['SOBE'] = $this;
127
128 $this->init();
129 }
130
131 /**
132 * Initialises the Class
133 *
134 * @return void
135 */
136 protected function init()
137 {
138 // Setting GET vars (used in frameset script):
139 $this->P = GeneralUtility::_GP('P');
140 // Setting GET vars (used in colorpicker script):
141 $this->colorValue = GeneralUtility::_GP('colorValue');
142 $this->fieldChangeFunc = GeneralUtility::_GP('fieldChangeFunc');
143 $this->fieldChangeFuncHash = GeneralUtility::_GP('fieldChangeFuncHash');
144 $this->fieldName = GeneralUtility::_GP('fieldName');
145 $this->formName = GeneralUtility::_GP('formName');
146 $this->md5ID = GeneralUtility::_GP('md5ID');
147 $this->exampleImg = GeneralUtility::_GP('exampleImg');
148 // Resolving image (checking existence etc.)
149 $this->imageError = '';
150 if ($this->exampleImg) {
151 $this->pickerImage = GeneralUtility::getFileAbsFileName($this->exampleImg, 1, 1);
152 if (!$this->pickerImage || !@is_file($this->pickerImage)) {
153 $this->imageError = 'ERROR: The image, "' . $this->exampleImg . '", could not be found!';
154 }
155 }
156 $update = array();
157 if ($this->areFieldChangeFunctionsValid()) {
158 // Setting field-change functions:
159 $fieldChangeFuncArr = unserialize($this->fieldChangeFunc);
160 unset($fieldChangeFuncArr['alert']);
161 foreach ($fieldChangeFuncArr as $v) {
162 $update[] = 'parent.opener.' . $v;
163 }
164 }
165 // Initialize document object:
166 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
167 $this->getPageRenderer()->loadRequireJsModule(
168 'TYPO3/CMS/Backend/Wizard/Colorpicker',
169 'function(Colorpicker) {
170 Colorpicker.setFieldChangeFunctions({
171 fieldChangeFunctions: function() {'
172 . implode('', $update) .
173 '}
174 });
175 }'
176 );
177 // Start page:
178 $this->content .= $this->doc->startPage($this->getLanguageService()->getLL('colorpicker_title'));
179 }
180
181 /**
182 * Injects the request object for the current request or subrequest
183 * As this controller goes only through the main() method, it is rather simple for now
184 *
185 * @param ServerRequestInterface $request
186 * @param ResponseInterface $response
187 * @return ResponseInterface
188 */
189 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
190 {
191 $this->main();
192
193 $this->content .= $this->doc->endPage();
194 $this->content = $this->doc->insertStylesAndJS($this->content);
195
196 $response->getBody()->write($this->content);
197 return $response;
198 }
199
200 /**
201 * Main Method, rendering either colorpicker or frameset depending on ->showPicker
202 *
203 * @return void
204 */
205 public function main()
206 {
207 // Show frameset by default:
208 if (!GeneralUtility::_GP('showPicker')) {
209 $this->frameSet();
210 } else {
211 // Putting together the items into a form:
212 $content = '
213 <form name="colorform" method="post" action="' . htmlspecialchars(BackendUtility::getModuleUrl('wizard_colorpicker')) . '">
214 ' . $this->colorMatrix() . '
215 ' . $this->colorList() . '
216 ' . $this->colorImage() . '
217
218 <!-- Value box: -->
219 <p class="c-head">' . $this->getLanguageService()->getLL('colorpicker_colorValue', true) . '</p>
220 <table border="0" cellpadding="0" cellspacing="3">
221 <tr>
222 <td>
223 <input id="colorValue" type="text" ' . $this->doc->formWidth(7) . ' maxlength="10" name="colorValue" value="' . htmlspecialchars($this->colorValue) . '" />
224 </td>
225 <td style="background-color:' . htmlspecialchars($this->colorValue) . '; border: 1px solid black;">
226 <span style="color: black;">' . $this->getLanguageService()->getLL('colorpicker_black', true) . '</span>&nbsp;<span style="color: white;">' . $this->getLanguageService()->getLL('colorpicker_white', true) . '</span>
227 </td>
228 <td>
229 <input class="btn btn-default" type="submit" id="colorpicker-saveclose" value="' . $this->getLanguageService()->getLL('colorpicker_setClose', true) . '" />
230 </td>
231 </tr>
232 </table>
233
234 <!-- Hidden fields with values that has to be kept constant -->
235 <input type="hidden" name="showPicker" value="1" />
236 <input type="hidden" name="fieldChangeFunc" value="' . htmlspecialchars($this->fieldChangeFunc) . '" />
237 <input type="hidden" name="fieldChangeFuncHash" value="' . htmlspecialchars($this->fieldChangeFuncHash) . '" />
238 <input type="hidden" name="fieldName" value="' . htmlspecialchars($this->fieldName) . '" />
239 <input type="hidden" name="formName" value="' . htmlspecialchars($this->formName) . '" />
240 <input type="hidden" name="md5ID" value="' . htmlspecialchars($this->md5ID) . '" />
241 <input type="hidden" name="exampleImg" value="' . htmlspecialchars($this->exampleImg) . '" />
242 </form>';
243
244 $this->content .= '<h2>' . $this->getLanguageService()->getLL('colorpicker_title', true) . '</h2>';
245 $this->content .= $content;
246 }
247 }
248
249 /**
250 * Returns the sourcecode to the browser
251 *
252 * @return void
253 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8, use mainAction() instead
254 */
255 public function printContent()
256 {
257 GeneralUtility::logDeprecatedFunction();
258 $this->content .= $this->doc->endPage();
259 $this->content = $this->doc->insertStylesAndJS($this->content);
260 echo $this->content;
261 }
262
263 /**
264 * Returns a frameset so our JavaScript Reference isn't lost
265 * Took some brains to figure this one out ;-)
266 * If Peter wouldn't have been I would've gone insane...
267 *
268 * @return void
269 */
270 public function frameSet()
271 {
272 $this->getDocumentTemplate()->JScode = $this->getDocumentTemplate()->wrapScriptTags('
273 if (!window.opener) {
274 alert("ERROR: Sorry, no link to main window... Closing");
275 close();
276 }
277 ');
278 $this->getDocumentTemplate()->startPage($this->getLanguageService()->getLL('colorpicker_title'));
279
280 // URL for the inner main frame:
281 $url = BackendUtility::getModuleUrl(
282 'wizard_colorpicker',
283 array(
284 'showPicker' => 1,
285 'colorValue' => $this->P['currentValue'],
286 'fieldName' => $this->P['itemName'],
287 'formName' => $this->P['formName'],
288 'exampleImg' => $this->P['exampleImg'],
289 'md5ID' => $this->P['md5ID'],
290 'fieldChangeFunc' => serialize($this->P['fieldChangeFunc']),
291 'fieldChangeFuncHash' => $this->P['fieldChangeFuncHash'],
292 )
293 );
294 $this->content = $this->getPageRenderer()->render(PageRenderer::PART_HEADER) . '
295 <frameset rows="*,1" framespacing="0" frameborder="0" border="0">
296 <frame name="content" src="' . htmlspecialchars($url) . '" marginwidth="0" marginheight="0" frameborder="0" scrolling="auto" noresize="noresize" />
297 <frame name="menu" src="' . htmlspecialchars(BackendUtility::getModuleUrl('dummy')) . '" marginwidth="0" marginheight="0" frameborder="0" scrolling="no" noresize="noresize" />
298 </frameset>
299 </html>';
300 }
301
302 /************************************
303 *
304 * Rendering of various color selectors
305 *
306 ************************************/
307 /**
308 * Creates a color matrix table
309 *
310 * @return string
311 */
312 public function colorMatrix()
313 {
314 $steps = 51;
315 // Get colors:
316 $color = array();
317 for ($rr = 0; $rr < 256; $rr += $steps) {
318 for ($gg = 0; $gg < 256; $gg += $steps) {
319 for ($bb = 0; $bb < 256; $bb += $steps) {
320 $color[] = '#' . substr(('0' . dechex($rr)), -2) . substr(('0' . dechex($gg)), -2) . substr(('0' . dechex($bb)), -2);
321 }
322 }
323 }
324 // Traverse colors:
325 $columns = 24;
326 $rows = 0;
327 $tRows = array();
328 while (isset($color[$columns * $rows])) {
329 $tCells = array();
330 for ($i = 0; $i < $columns; $i++) {
331 $tCells[] = '<td bgcolor="' . $color[($columns * $rows + $i)] . '" class="t3js-colorpicker-value" data-color-value="' . htmlspecialchars($color[($columns * $rows + $i)]) . '" title="' . htmlspecialchars($color[($columns * $rows + $i)]) . '">&nbsp;&nbsp;</td>';
332 }
333 $tRows[] = '<tr>' . implode('', $tCells) . '</tr>';
334 $rows++;
335 }
336 return '<p class="c-head">' . $this->getLanguageService()->getLL('colorpicker_fromMatrix', true) . '</p>
337 <table style="width:100%; border: 1px solid black; cursor:crosshair;">' . implode('', $tRows) . '</table>';
338 }
339
340 /**
341 * Creates a selector box with all HTML color names.
342 *
343 * @return string
344 */
345 public function colorList()
346 {
347 // Initialize variables:
348 $colors = explode(',', $this->HTMLcolorList);
349 $currentValue = strtolower($this->colorValue);
350 $opt = array();
351 $opt[] = '<option value=""></option>';
352 // Traverse colors, making option tags for selector box.
353 foreach ($colors as $colorName) {
354 $opt[] = '<option style="background-color: ' . $colorName . ';" value="' . htmlspecialchars($colorName) . '"' . ($currentValue === $colorName ? ' selected="selected"' : '') . '>' . htmlspecialchars($colorName) . '</option>';
355 }
356 // Compile selector box and return result:
357 return '<p class="c-head">' . $this->getLanguageService()->getLL('colorpicker_fromList', true) . '</p>
358 <select class="t3js-colorpicker-selector">' . implode(LF, $opt) . '</select><br />';
359 }
360
361 /**
362 * Creates a color image selector
363 *
364 * @return string
365 */
366 public function colorImage()
367 {
368 // Handling color-picker image if any:
369 if (!$this->imageError) {
370 if ($this->pickerImage) {
371 if (GeneralUtility::_POST('coords_x')) {
372 /** @var $image \TYPO3\CMS\Core\Imaging\GraphicalFunctions */
373 $image = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\GraphicalFunctions::class);
374 $this->colorValue = '#' . $this->getIndex($image->imageCreateFromFile($this->pickerImage), GeneralUtility::_POST('coords_x'), GeneralUtility::_POST('coords_y'));
375 }
376 $pickerFormImage = '
377 <p class="c-head">' . $this->getLanguageService()->getLL('colorpicker_fromImage', true) . '</p>
378 <input type="image" src="../' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($this->pickerImage) . '" name="coords" style="cursor:crosshair;" /><br />';
379 } else {
380 $pickerFormImage = '';
381 }
382 } else {
383 $pickerFormImage = '
384 <p class="c-head">' . htmlspecialchars($this->imageError) . '</p>';
385 }
386 return $pickerFormImage;
387 }
388
389 /**
390 * Gets the HTML (Hex) Color Code for the selected pixel of an image
391 * This method handles the correct imageResource no matter what format
392 *
393 * @param resource $im Valid ImageResource returned by \TYPO3\CMS\Core\Imaging\GraphicalFunctions::imageCreateFromFile
394 * @param int $x X-Coordinate of the pixel that should be checked
395 * @param int $y Y-Coordinate of the pixel that should be checked
396 * @return string HEX RGB value for color
397 * @see colorImage()
398 */
399 public function getIndex($im, $x, $y)
400 {
401 $rgb = ImageColorAt($im, $x, $y);
402 $colorRgb = imagecolorsforindex($im, $rgb);
403 $index['r'] = dechex($colorRgb['red']);
404 $index['g'] = dechex($colorRgb['green']);
405 $index['b'] = dechex($colorRgb['blue']);
406 $hexValue = array();
407 foreach ($index as $value) {
408 if (strlen($value) === 1) {
409 $hexValue[] = strtoupper('0' . $value);
410 } else {
411 $hexValue[] = strtoupper($value);
412 }
413 }
414 $hex = implode('', $hexValue);
415 return $hex;
416 }
417
418 /**
419 * Determines whether submitted field change functions are valid
420 * and are coming from the system and not from an external abuse.
421 *
422 * @return bool Whether the submitted field change functions are valid
423 */
424 protected function areFieldChangeFunctionsValid()
425 {
426 return $this->fieldChangeFunc && $this->fieldChangeFuncHash && $this->fieldChangeFuncHash === GeneralUtility::hmac($this->fieldChangeFunc);
427 }
428
429 /**
430 * @return PageRenderer
431 */
432 protected function getPageRenderer()
433 {
434 return GeneralUtility::makeInstance(PageRenderer::class);
435 }
436 }