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