[!!!][FEATURE] Introduce PSR-7-based Routing for Backend AJAX Requests
[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\Utility\BackendUtility;
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 use TYPO3\CMS\Extbase\Utility\ArrayUtility;
25
26 /**
27 * Generation of image manipulation TCEform element
28 */
29 class ImageManipulationElement extends AbstractFormElement {
30
31 /**
32 * Default element configuration
33 *
34 * @var array
35 */
36 protected $defaultConfig = array(
37 'file_field' => 'uid_local',
38 'enableZoom' => FALSE,
39 'allowedExtensions' => NULL, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
40 'ratios' => array(
41 '1.7777777777777777' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.16_9',
42 '1.3333333333333333' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.4_3',
43 '1' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.1_1',
44 'NaN' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.free',
45 )
46 );
47
48 /**
49 * Handler for unknown types.
50 *
51 * @return array As defined in initializeResultArray() of AbstractNode
52 */
53 public function render() {
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 = ArrayUtility::arrayMergeRecursiveOverrule($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 = array();
73 $options['parameterArray'] = array(
74 'fieldConf' => array(
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(mb_strtolower($config['allowedExtensions']), mb_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] = $languageService->sL($label, TRUE);
98 }
99
100 $formFieldId = StringUtility::getUniqueId('formengine-image-manipulation-');
101 $wizardData = array(
102 'file' => $file->getUid(),
103 'zoom' => $config['enableZoom'] ? '1' : '0',
104 'ratios' => json_encode($config['ratios']),
105 );
106 $wizardData['token'] = GeneralUtility::hmac(implode('|', $wizardData), 'ImageManipulationWizard');
107
108 $buttonAttributes = array(
109 'data-url' => BackendUtility::getAjaxUrl('wizard_image_manipulation', $wizardData),
110 'data-severity' => 'notice',
111 'data-image-name' => $file->getNameWithoutExtension(),
112 'data-image-uid' => $file->getUid(),
113 'data-file-field' => $config['file_field'],
114 'data-field' => $formFieldId,
115 );
116
117 $button = '<button class="btn btn-default t3js-image-manipulation-trigger"';
118 foreach ($buttonAttributes as $key => $value) {
119 $button .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
120 }
121 $button .= '><span class="t3-icon fa fa-crop"></span>';
122 $button .= $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.open-editor', TRUE);
123 $button .= '</button>';
124
125 $inputField = '<input type="hidden" '
126 . 'id="' . $formFieldId . '" '
127 . 'name="' . $parameterArray['itemFormElName'] . '" '
128 . 'value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
129
130 $content .= $inputField . $button;
131
132 $content .= $this->getImageManipulationInfoTable($parameterArray['itemFormElValue']);
133
134 $resultArray['requireJsModules'][] = array(
135 'TYPO3/CMS/Backend/ImageManipulation' => 'function(ImageManipulation){ImageManipulation.initializeTrigger()}'
136 );
137 }
138
139 $content .= '<p class="text-muted"><em>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.supported-types-message', TRUE) . '<br />';
140 $content .= mb_strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'])));
141 $content .= '</em></p>';
142
143 $item = '<div class="media">';
144 $item .= $preview;
145 $item .= '<div class="media-body">' . $content . '</div>';
146 $item .= '</div>';
147
148 $resultArray['html'] = $item;
149 return $resultArray;
150 }
151
152 /**
153 * Get file object
154 *
155 * @param array $row
156 * @param string $fieldName
157 * @return NULL|\TYPO3\CMS\Core\Resource\File
158 */
159 protected function getFile(array $row, $fieldName) {
160 $file = NULL;
161 $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : NULL;
162 if (strpos($fileUid, 'sys_file_') === 0) {
163 if (strpos($fileUid, '|')) {
164 // @todo: uid_local is a group field that was resolved to table_uid|target - split here again
165 // @todo: this will vanish if group fields are moved to array
166 $fileUid = explode('|', $fileUid);
167 $fileUid = $fileUid[0];
168 }
169 $fileUid = substr($fileUid, 9);
170 }
171 if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
172 try {
173 $file = ResourceFactory::getInstance()->getFileObject($fileUid);
174 } catch (FileDoesNotExistException $e) {
175 }
176 }
177 return $file;
178 }
179
180 /**
181 * Get preview image if cropping is set
182 *
183 * @param File $file
184 * @param string $crop
185 * @return string
186 */
187 public function getPreview(File $file, $crop) {
188 $thumbnail = '';
189 $maxWidth = 150;
190 $maxHeight = 200;
191 if ($crop) {
192 $imageSetup = array('maxWidth' => $maxWidth, 'maxHeight' => $maxHeight, 'crop' => $crop);
193 $processedImage = $file->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
194 // Only use a thumbnail if the processing process was successful by checking if image width is set
195 if ($processedImage->getProperty('width')) {
196 $imageUrl = $processedImage->getPublicUrl(TRUE);
197 $thumbnail = '<img src="' . $imageUrl . '" ' .
198 'class="thumbnail thumbnail-status" ' .
199 'width="' . $processedImage->getProperty('width') . '" ' .
200 'height="' . $processedImage->getProperty('height') . '" >';
201 }
202 }
203
204 $preview = '<div class="media-left">';
205 $preview .= '<div class="t3js-image-manipulation-preview media-object' . ($thumbnail ? '' : ' hide'). '" ';
206 // Set preview width/height needed by cropper
207 $preview .= 'data-preview-width="' . $maxWidth . '" data-preview-height="' . $maxHeight . '">';
208 $preview .= $thumbnail;
209 $preview .= '</div></div>';
210
211 return $preview;
212 }
213
214 /**
215 * Get image manipulation info table
216 *
217 * @param string $rawImageManipulationValue
218 * @return string
219 */
220 protected function getImageManipulationInfoTable($rawImageManipulationValue) {
221 $content = '';
222 $imageManipulation = NULL;
223 $x = $y = $width = $height = 0;
224
225 // Determine cropping values
226 if ($rawImageManipulationValue) {
227 $imageManipulation = json_decode($rawImageManipulationValue);
228 if (is_object($imageManipulation)) {
229 $x = (int)$imageManipulation->x;
230 $y = (int)$imageManipulation->y;
231 $width = (int)$imageManipulation->width;
232 $height = (int)$imageManipulation->height;
233 } else {
234 $imageManipulation = NULL;
235 }
236 }
237 $languageService = $this->getLanguageService();
238
239 $content .= '<div class="table-fit-block table-spacer-wrap">';
240 $content .= '<table class="table table-no-borders t3js-image-manipulation-info'. ($imageManipulation === NULL ? ' hide' : '') . '">';
241 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x', TRUE) . '</td>';
242 $content .= '<td class="t3js-image-manipulation-info-crop-x">' . $x . 'px</td></tr>';
243 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y', TRUE) . '</td>';
244 $content .= '<td class="t3js-image-manipulation-info-crop-y">' . $y . 'px</td></tr>';
245 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width', TRUE) . '</td>';
246 $content .= '<td class="t3js-image-manipulation-info-crop-width">' . $width . 'px</td></tr>';
247 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height', TRUE) . '</td>';
248 $content .= '<td class="t3js-image-manipulation-info-crop-height">' . $height . 'px</td></tr>';
249 $content .= '</table>';
250 $content .= '</div>';
251
252 return $content;
253 }
254 }