[BUGFIX] Catch some FAL exceptions for missing driver
[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 {
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 = ArrayUtility::arrayMergeRecursiveOverrule($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 = array();
74 $options['parameterArray'] = array(
75 'fieldConf' => array(
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(mb_strtolower($config['allowedExtensions']), mb_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] = $languageService->sL($label, true);
99 }
100
101 $formFieldId = StringUtility::getUniqueId('formengine-image-manipulation-');
102 $wizardData = array(
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 $buttonAttributes = array(
110 'data-url' => BackendUtility::getAjaxUrl('wizard_image_manipulation', $wizardData),
111 'data-severity' => 'notice',
112 'data-image-name' => $file->getNameWithoutExtension(),
113 'data-image-uid' => $file->getUid(),
114 'data-file-field' => $config['file_field'],
115 'data-field' => $formFieldId,
116 );
117
118 $button = '<button class="btn btn-default t3js-image-manipulation-trigger"';
119 foreach ($buttonAttributes as $key => $value) {
120 $button .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
121 }
122 $button .= '><span class="t3-icon fa fa-crop"></span>';
123 $button .= $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.open-editor', true);
124 $button .= '</button>';
125
126 $inputField = '<input type="hidden" '
127 . 'id="' . $formFieldId . '" '
128 . 'name="' . $parameterArray['itemFormElName'] . '" '
129 . 'value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
130
131 $content .= $inputField . $button;
132
133 $content .= $this->getImageManipulationInfoTable($parameterArray['itemFormElValue']);
134
135 $resultArray['requireJsModules'][] = array(
136 'TYPO3/CMS/Backend/ImageManipulation' => 'function(ImageManipulation){ImageManipulation.initializeTrigger()}'
137 );
138 }
139
140 $content .= '<p class="text-muted"><em>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.supported-types-message', true) . '<br />';
141 $content .= mb_strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'])));
142 $content .= '</em></p>';
143
144 $item = '<div class="media">';
145 $item .= $preview;
146 $item .= '<div class="media-body">' . $content . '</div>';
147 $item .= '</div>';
148
149 $resultArray['html'] = $item;
150 return $resultArray;
151 }
152
153 /**
154 * Get file object
155 *
156 * @param array $row
157 * @param string $fieldName
158 * @return NULL|\TYPO3\CMS\Core\Resource\File
159 */
160 protected function getFile(array $row, $fieldName)
161 {
162 $file = null;
163 $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null;
164 if (strpos($fileUid, 'sys_file_') === 0) {
165 if (strpos($fileUid, '|')) {
166 // @todo: uid_local is a group field that was resolved to table_uid|target - split here again
167 // @todo: this will vanish if group fields are moved to array
168 $fileUid = explode('|', $fileUid);
169 $fileUid = $fileUid[0];
170 }
171 $fileUid = substr($fileUid, 9);
172 }
173 if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
174 try {
175 $file = ResourceFactory::getInstance()->getFileObject($fileUid);
176 } catch (FileDoesNotExistException $e) {
177 } catch (\InvalidArgumentException $e) {
178 }
179 }
180 return $file;
181 }
182
183 /**
184 * Get preview image if cropping is set
185 *
186 * @param File $file
187 * @param string $crop
188 * @return string
189 */
190 public function getPreview(File $file, $crop)
191 {
192 $thumbnail = '';
193 $maxWidth = 150;
194 $maxHeight = 200;
195 if ($crop) {
196 $imageSetup = array('maxWidth' => $maxWidth, 'maxHeight' => $maxHeight, 'crop' => $crop);
197 $processedImage = $file->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
198 // Only use a thumbnail if the processing process was successful by checking if image width is set
199 if ($processedImage->getProperty('width')) {
200 $imageUrl = $processedImage->getPublicUrl(true);
201 $thumbnail = '<img src="' . $imageUrl . '" ' .
202 'class="thumbnail thumbnail-status" ' .
203 'width="' . $processedImage->getProperty('width') . '" ' .
204 'height="' . $processedImage->getProperty('height') . '" >';
205 }
206 }
207
208 $preview = '<div class="media-left">';
209 $preview .= '<div class="t3js-image-manipulation-preview media-object' . ($thumbnail ? '' : ' hide') . '" ';
210 // Set preview width/height needed by cropper
211 $preview .= 'data-preview-width="' . $maxWidth . '" data-preview-height="' . $maxHeight . '">';
212 $preview .= $thumbnail;
213 $preview .= '</div></div>';
214
215 return $preview;
216 }
217
218 /**
219 * Get image manipulation info table
220 *
221 * @param string $rawImageManipulationValue
222 * @return string
223 */
224 protected function getImageManipulationInfoTable($rawImageManipulationValue)
225 {
226 $content = '';
227 $imageManipulation = null;
228 $x = $y = $width = $height = 0;
229
230 // Determine cropping values
231 if ($rawImageManipulationValue) {
232 $imageManipulation = json_decode($rawImageManipulationValue);
233 if (is_object($imageManipulation)) {
234 $x = (int)$imageManipulation->x;
235 $y = (int)$imageManipulation->y;
236 $width = (int)$imageManipulation->width;
237 $height = (int)$imageManipulation->height;
238 } else {
239 $imageManipulation = null;
240 }
241 }
242 $languageService = $this->getLanguageService();
243
244 $content .= '<div class="table-fit-block table-spacer-wrap">';
245 $content .= '<table class="table table-no-borders t3js-image-manipulation-info' . ($imageManipulation === null ? ' hide' : '') . '">';
246 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x', true) . '</td>';
247 $content .= '<td class="t3js-image-manipulation-info-crop-x">' . $x . 'px</td></tr>';
248 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y', true) . '</td>';
249 $content .= '<td class="t3js-image-manipulation-info-crop-y">' . $y . 'px</td></tr>';
250 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width', true) . '</td>';
251 $content .= '<td class="t3js-image-manipulation-info-crop-width">' . $width . 'px</td></tr>';
252 $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height', true) . '</td>';
253 $content .= '<td class="t3js-image-manipulation-info-crop-height">' . $height . 'px</td></tr>';
254 $content .= '</table>';
255 $content .= '</div>';
256
257 return $content;
258 }
259 }