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