TYPO3 CMS  TYPO3_8-7
ImageManipulationElement.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
30 
36 {
40  private $wizardRouteName = 'ajax_wizard_image_manipulation';
41 
47  protected static $defaultConfig = [
48  'file_field' => 'uid_local',
49  'allowedExtensions' => null, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
50  'cropVariants' => [
51  'default' => [
52  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.crop_variant.default',
53  'allowedAspectRatios' => [
54  '16:9' => [
55  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.16_9',
56  'value' => 16 / 9
57  ],
58  '3:2' => [
59  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.3_2',
60  'value' => 3 / 2
61  ],
62  '4:3' => [
63  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.4_3',
64  'value' => 4 / 3
65  ],
66  '1:1' => [
67  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.1_1',
68  'value' => 1.0
69  ],
70  'NaN' => [
71  'title' => 'LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:imwizard.ratio.free',
72  'value' => 0.0
73  ],
74  ],
75  'selectedRatio' => 'NaN',
76  'cropArea' => [
77  'x' => 0.0,
78  'y' => 0.0,
79  'width' => 1.0,
80  'height' => 1.0,
81  ],
82  ],
83  ]
84  ];
85 
91  protected $defaultFieldWizard = [
92  'localizationStateSelector' => [
93  'renderType' => 'localizationStateSelector',
94  ],
95  'otherLanguageContent' => [
96  'renderType' => 'otherLanguageContent',
97  'after' => [
98  'localizationStateSelector'
99  ],
100  ],
101  'defaultLanguageDifferences' => [
102  'renderType' => 'defaultLanguageDifferences',
103  'after' => [
104  'otherLanguageContent',
105  ],
106  ],
107  ];
108 
112  protected $templateView;
113 
117  protected $uriBuilder;
118 
123  public function __construct(NodeFactory $nodeFactory, array $data)
124  {
125  parent::__construct($nodeFactory, $data);
126  // Would be great, if we could inject the view here, but since the constructor is in the interface, we can't
127  $this->templateView = GeneralUtility::makeInstance(StandaloneView::class);
128  $this->templateView->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts/')]);
129  $this->templateView->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials/ImageManipulation/')]);
130  $this->templateView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/ImageManipulation/ImageManipulationElement.html'));
131  $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
132  }
133 
140  public function render()
141  {
142  $resultArray = $this->initializeResultArray();
143  $parameterArray = $this->data['parameterArray'];
144  $config = $this->populateConfiguration($parameterArray['fieldConf']['config']);
145 
146  $file = $this->getFile($this->data['databaseRow'], $config['file_field']);
147  if (!$file) {
148  // Early return in case we do not find a file
149  return $resultArray;
150  }
151 
152  $config = $this->processConfiguration($config, $parameterArray['itemFormElValue'], $file);
153 
154  $fieldInformationResult = $this->renderFieldInformation();
155  $fieldInformationHtml = $fieldInformationResult['html'];
156  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
157 
158  $fieldControlResult = $this->renderFieldControl();
159  $fieldControlHtml = $fieldControlResult['html'];
160  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
161 
162  $fieldWizardResult = $this->renderFieldWizard();
163  $fieldWizardHtml = $fieldWizardResult['html'];
164  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
165 
166  $arguments = [
167  'fieldInformation' => $fieldInformationHtml,
168  'fieldControl' => $fieldControlHtml,
169  'fieldWizard' => $fieldWizardHtml,
170  'isAllowedFileExtension' => in_array(strtolower($file->getExtension()), GeneralUtility::trimExplode(',', strtolower($config['allowedExtensions'])), true),
171  'image' => $file,
172  'formEngine' => [
173  'field' => [
174  'value' => $parameterArray['itemFormElValue'],
175  'name' => $parameterArray['itemFormElName']
176  ],
177  'validation' => '[]'
178  ],
179  'config' => $config,
180  'wizardUri' => $this->getWizardUri(),
181  'wizardPayload' => json_encode($this->getWizardPayload($config['cropVariants'], $file)),
182  'previewUrl' => $this->getPreviewUrl($this->data['databaseRow'], $file),
183  ];
184 
185  if ($arguments['isAllowedFileExtension']) {
186  $resultArray['requireJsModules'][] = [
187  'TYPO3/CMS/Backend/ImageManipulation' => 'function (ImageManipulation) {top.require(["cropper"], function() { ImageManipulation.initializeTrigger(); }); }'
188  ];
189  $arguments['formEngine']['field']['id'] = StringUtility::getUniqueId('formengine-image-manipulation-');
190  if (GeneralUtility::inList($config['eval'], 'required')) {
191  $arguments['formEngine']['validation'] = $this->getValidationDataAsJsonString(['required' => true]);
192  }
193  }
194  $this->templateView->assignMultiple($arguments);
195  $resultArray['html'] = $this->templateView->render();
196 
197  return $resultArray;
198  }
199 
207  protected function getFile(array $row, $fieldName)
208  {
209  $file = null;
210  $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null;
211  if (is_array($fileUid) && isset($fileUid[0]['uid'])) {
212  $fileUid = $fileUid[0]['uid'];
213  }
215  try {
216  $file = ResourceFactory::getInstance()->getFileObject($fileUid);
217  } catch (FileDoesNotExistException $e) {
218  } catch (\InvalidArgumentException $e) {
219  }
220  }
221  return $file;
222  }
223 
229  protected function getPreviewUrl(array $databaseRow, File $file): string
230  {
231  $previewUrl = '';
232  // Hook to generate a preview URL
233  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend/Form/Element/ImageManipulationElement']['previewUrl']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend/Form/Element/ImageManipulationElement']['previewUrl'])) {
234  $hookParameters = [
235  'databaseRow' => $databaseRow,
236  'file' => $file,
237  'previewUrl' => $previewUrl,
238  ];
239  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend/Form/Element/ImageManipulationElement']['previewUrl'] as $listener) {
240  $previewUrl = GeneralUtility::callUserFunction($listener, $hookParameters, $this);
241  }
242  }
243  return $previewUrl;
244  }
245 
251  protected function populateConfiguration(array $baseConfiguration)
252  {
253  $defaultConfig = self::$defaultConfig;
254 
255  // If ratios are set do not add default options
256  if (isset($baseConfiguration['cropVariants'])) {
257  unset($defaultConfig['cropVariants']);
258  }
259 
260  $config = array_replace_recursive($defaultConfig, $baseConfiguration);
261 
262  if (!is_array($config['cropVariants'])) {
263  throw new InvalidConfigurationException('Crop variants configuration must be an array', 1485377267);
264  }
265 
266  $cropVariants = [];
267  foreach ($config['cropVariants'] as $id => $cropVariant) {
268  // Ignore disabled crop variants
269  if (!empty($cropVariant['disabled'])) {
270  continue;
271  }
272  // Enforce a crop area (default is full image)
273  if (empty($cropVariant['cropArea'])) {
274  $cropVariant['cropArea'] = Area::createEmpty()->asArray();
275  }
276  $cropVariants[$id] = $cropVariant;
277  }
278 
279  $config['cropVariants'] = $cropVariants;
280 
281  // By default we allow all image extensions that can be handled by the GFX functionality
282  if ($config['allowedExtensions'] === null) {
283  $config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
284  }
285  return $config;
286  }
287 
295  protected function processConfiguration(array $config, string &$elementValue, File $file)
296  {
297  $cropVariantCollection = CropVariantCollection::create($elementValue, $config['cropVariants']);
298  if (empty($config['readOnly']) && !empty($file->getProperty('width'))) {
299  $cropVariantCollection = $cropVariantCollection->applyRatioRestrictionToSelectedCropArea($file);
300  $elementValue = (string)$cropVariantCollection;
301  }
302  $config['cropVariants'] = $cropVariantCollection->asArray();
303  $config['allowedExtensions'] = implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'], true));
304  return $config;
305  }
306 
310  protected function getWizardUri(): string
311  {
312  return (string)$this->uriBuilder->buildUriFromRoute($this->wizardRouteName);
313  }
314 
320  protected function getWizardPayload(array $cropVariants, File $image): array
321  {
322  $arguments = [
323  'cropVariants' => $cropVariants,
324  'image' => $image->getUid(),
325  ];
326  $uriArguments['arguments'] = json_encode($arguments);
327  $uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $this->wizardRouteName);
328 
329  return $uriArguments;
330  }
331 }
processConfiguration(array $config, string &$elementValue, File $file)
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static hmac($input, $additionalSecret='')
static getFileAbsFileName($filename, $_=null, $_2=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
static create(string $jsonString, array $tcaConfig=[])
mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']