[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / ImageManipulationElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
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\Routing\UriBuilder;
18 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
19 use TYPO3\CMS\Core\Resource\File;
20 use TYPO3\CMS\Core\Resource\ResourceFactory;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\MathUtility;
23 use TYPO3\CMS\Core\Utility\StringUtility;
24
25 /**
26 * Generation of image manipulation FormEngine element.
27 * This is typically used in FAL relations to cut images.
28 */
29 class ImageManipulationElement extends AbstractFormElement
30 {
31 /**
32 * Default element configuration
33 *
34 * @var array
35 */
36 protected $defaultConfig = [
37 'file_field' => 'uid_local',
38 'enableZoom' => false,
39 'allowedExtensions' => null, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
40 'ratios' => [
41 '1.7777777777777777' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.16_9',
42 '1.3333333333333333' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.4_3',
43 '1' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.1_1',
44 'NaN' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.free',
45 ]
46 ];
47
48 /**
49 * This will render an imageManipulation field
50 *
51 * @return array As defined in initializeResultArray() of AbstractNode
52 */
53 public function render()
54 {
55 $resultArray = $this->initializeResultArray();
56 $languageService = $this->getLanguageService();
57
58 $row = $this->data['databaseRow'];
59 $parameterArray = $this->data['parameterArray'];
60
61 // If ratios are set do not add default options
62 if (isset($parameterArray['fieldConf']['config']['ratios'])) {
63 unset($this->defaultConfig['ratios']);
64 }
65 $config = array_replace_recursive($this->defaultConfig, $parameterArray['fieldConf']['config']);
66
67 // By default we allow all image extensions that can be handled by the GFX functionality
68 if ($config['allowedExtensions'] === null) {
69 $config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
70 }
71
72 if ($config['readOnly']) {
73 $options = [];
74 $options['parameterArray'] = [
75 'fieldConf' => [
76 'config' => $config,
77 ],
78 'itemFormElValue' => $parameterArray['itemFormElValue'],
79 ];
80 $options['renderType'] = 'none';
81 return $this->nodeFactory->create($options)->render();
82 }
83
84 $file = $this->getFile($row, $config['file_field']);
85 if (!$file) {
86 return $resultArray;
87 }
88
89 $content = '';
90 $preview = '';
91 if (GeneralUtility::inList(strtolower($config['allowedExtensions']), strtolower($file->getExtension()))) {
92
93 // Get preview
94 $preview = $this->getPreview($file, $parameterArray['itemFormElValue']);
95
96 // Check if ratio labels hold translation strings
97 foreach ((array)$config['ratios'] as $ratio => $label) {
98 $config['ratios'][$ratio] = htmlspecialchars($languageService->sL($label));
99 }
100
101 $formFieldId = StringUtility::getUniqueId('formengine-image-manipulation-');
102 $wizardData = [
103 'zoom' => $config['enableZoom'] ? '1' : '0',
104 'ratios' => json_encode($config['ratios']),
105 'file' => $file->getUid(),
106 ];
107 $wizardData['token'] = GeneralUtility::hmac(implode('|', $wizardData), 'ImageManipulationWizard');
108
109 /** @var UriBuilder $uriBuilder */
110 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
111 $buttonAttributes = [
112 'data-url' => $uriBuilder->buildUriFromRoute('ajax_wizard_image_manipulation', $wizardData),
113 'data-severity' => 'notice',
114 'data-image-name' => $file->getNameWithoutExtension(),
115 'data-image-uid' => $file->getUid(),
116 'data-file-field' => $config['file_field'],
117 'data-field' => $formFieldId,
118 ];
119
120 $button = '<button class="btn btn-default t3js-image-manipulation-trigger"';
121 $button .= GeneralUtility::implodeAttributes($buttonAttributes, true, true);
122 $button .= '><span class="t3-icon fa fa-crop"></span>';
123 $button .= htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.open-editor'));
124 $button .= '</button>';
125
126 $attributes = [];
127 $attributes['type'] = 'hidden';
128 $attributes['id'] = $formFieldId;
129 $attributes['name'] = $parameterArray['itemFormElName'];
130 $attributes['value'] = $parameterArray['itemFormElValue'];
131
132 $evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
133 if (in_array('required', $evalList, true)) {
134 $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString(['required' => true]);
135 }
136
137 $inputField = '<input ' . GeneralUtility::implodeAttributes($attributes, true, true) . '" />';
138
139 $content .= $inputField . $button;
140
141 $content .= $this->getImageManipulationInfoTable($parameterArray['itemFormElValue']);
142
143 $resultArray['requireJsModules'][] = [
144 'TYPO3/CMS/Backend/ImageManipulation' => 'function(ImageManipulation){ImageManipulation.initializeTrigger()}'
145 ];
146 }
147
148 $content .= '<p class="text-muted"><em>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.supported-types-message')) . '<br />';
149 $content .= strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'])));
150 $content .= '</em></p>';
151
152 $item = '<div class="media">';
153 $item .= $preview;
154 $item .= '<div class="media-body">' . $content . '</div>';
155 $item .= '</div>';
156
157 $resultArray['html'] = $item;
158 return $resultArray;
159 }
160
161 /**
162 * Get file object
163 *
164 * @param array $row
165 * @param string $fieldName
166 * @return NULL|\TYPO3\CMS\Core\Resource\File
167 */
168 protected function getFile(array $row, $fieldName)
169 {
170 $file = null;
171 $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null;
172 if (is_array($fileUid) && isset($fileUid[0]['uid'])) {
173 $fileUid = $fileUid[0]['uid'];
174 }
175 if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
176 try {
177 $file = ResourceFactory::getInstance()->getFileObject($fileUid);
178 } catch (FileDoesNotExistException $e) {
179 } catch (\InvalidArgumentException $e) {
180 }
181 }
182 return $file;
183 }
184
185 /**
186 * Get preview image if cropping is set
187 *
188 * @param File $file
189 * @param string $crop
190 * @return string
191 */
192 public function getPreview(File $file, $crop)
193 {
194 $thumbnail = '';
195 $maxWidth = 150;
196 $maxHeight = 200;
197 if ($crop) {
198 $imageSetup = ['maxWidth' => $maxWidth, 'maxHeight' => $maxHeight, 'crop' => $crop];
199 $processedImage = $file->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
200 // Only use a thumbnail if the processing process was successful by checking if image width is set
201 if ($processedImage->getProperty('width')) {
202 $imageUrl = $processedImage->getPublicUrl(true);
203 $thumbnail = '<img src="' . $imageUrl . '" ' .
204 'class="thumbnail thumbnail-status" ' .
205 'width="' . $processedImage->getProperty('width') . '" ' .
206 'height="' . $processedImage->getProperty('height') . '" >';
207 }
208 }
209
210 $preview = '<div class="media-left">';
211 $preview .= '<div class="t3js-image-manipulation-preview media-object' . ($thumbnail ? '' : ' hide') . '" ';
212 // Set preview width/height needed by cropper
213 $preview .= 'data-preview-width="' . $maxWidth . '" data-preview-height="' . $maxHeight . '">';
214 $preview .= $thumbnail;
215 $preview .= '</div></div>';
216
217 return $preview;
218 }
219
220 /**
221 * Get image manipulation info table
222 *
223 * @param string $rawImageManipulationValue
224 * @return string
225 */
226 protected function getImageManipulationInfoTable($rawImageManipulationValue)
227 {
228 $content = '';
229 $imageManipulation = null;
230 $x = $y = $width = $height = 0;
231
232 // Determine cropping values
233 if ($rawImageManipulationValue) {
234 $imageManipulation = json_decode($rawImageManipulationValue);
235 if (is_object($imageManipulation)) {
236 $x = (int)$imageManipulation->x;
237 $y = (int)$imageManipulation->y;
238 $width = (int)$imageManipulation->width;
239 $height = (int)$imageManipulation->height;
240 } else {
241 $imageManipulation = null;
242 }
243 }
244 $languageService = $this->getLanguageService();
245
246 $content .= '<div class="table-fit-block table-spacer-wrap">';
247 $content .= '<table class="table table-no-borders t3js-image-manipulation-info' . ($imageManipulation === null ? ' hide' : '') . '">';
248 $content .= '<tr><td>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop-x')) . '</td>';
249 $content .= '<td class="t3js-image-manipulation-info-crop-x">' . $x . 'px</td></tr>';
250 $content .= '<tr><td>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop-y')) . '</td>';
251 $content .= '<td class="t3js-image-manipulation-info-crop-y">' . $y . 'px</td></tr>';
252 $content .= '<tr><td>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop-width')) . '</td>';
253 $content .= '<td class="t3js-image-manipulation-info-crop-width">' . $width . 'px</td></tr>';
254 $content .= '<tr><td>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop-height')) . '</td>';
255 $content .= '<td class="t3js-image-manipulation-info-crop-height">' . $height . 'px</td></tr>';
256 $content .= '</table>';
257 $content .= '</div>';
258
259 return $content;
260 }
261 }