[BUGFIX] Send payload of image cropping wizard via POST 86/58786/3
authorAndreas Fernandez <a.fernandez@scripting-base.de>
Mon, 29 Oct 2018 22:17:55 +0000 (23:17 +0100)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Fri, 7 Dec 2018 17:58:52 +0000 (18:58 +0100)
The image cropper wizard configuration can become very large, which
might break the wizard as the configuration is sent via query parameters
and "Request-URI Too Long" might kick in.

The payload is now sent via POST to bypass this issue. As our Modal API
is currently not capable of sending AJAX requests via POST, the logic
regarding the icon spinner is duplicated for the time being.

Resolves: #82225
Releases: master, 8.7
Change-Id: I7106b62fcc09101bc5147277225d1b8e89133d5c
Reviewed-on: https://review.typo3.org/58786
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php
typo3/sysext/backend/Resources/Private/Templates/ImageManipulation/ImageManipulationElement.html
typo3/sysext/backend/Resources/Private/TypeScript/ImageManipulation.ts
typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js

index c5eb34f..86a7aba 100644 (file)
@@ -35,6 +35,11 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 class ImageManipulationElement extends AbstractFormElement
 {
     /**
+     * @var string
+     */
+    private $wizardRouteName = 'ajax_wizard_image_manipulation';
+
+    /**
      * Default element configuration
      *
      * @var array
@@ -172,7 +177,8 @@ class ImageManipulationElement extends AbstractFormElement
                 'validation' => '[]'
             ],
             'config' => $config,
-            'wizardUri' => $this->getWizardUri($config['cropVariants'], $file),
+            'wizardUri' => $this->getWizardUri(),
+            'wizardPayload' => json_encode($this->getWizardPayload($config['cropVariants'], $file)),
             'previewUrl' => $this->getPreviewUrl($this->data['databaseRow'], $file),
         ];
 
@@ -299,19 +305,27 @@ class ImageManipulationElement extends AbstractFormElement
     }
 
     /**
+     * @return string
+     */
+    protected function getWizardUri(): string
+    {
+        return (string)$this->uriBuilder->buildUriFromRoute($this->wizardRouteName);
+    }
+
+    /**
      * @param array $cropVariants
      * @param File $image
-     * @return string
+     * @return array
      */
-    protected function getWizardUri(array $cropVariants, File $image): string
+    protected function getWizardPayload(array $cropVariants, File $image): array
     {
-        $routeName = 'ajax_wizard_image_manipulation';
         $arguments = [
             'cropVariants' => $cropVariants,
             'image' => $image->getUid(),
         ];
         $uriArguments['arguments'] = json_encode($arguments);
-        $uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $routeName);
-        return (string)$this->uriBuilder->buildUriFromRoute($routeName, $uriArguments);
+        $uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $this->wizardRouteName);
+
+        return $uriArguments;
     }
 }
index 0b10301..11290f9 100644 (file)
@@ -57,8 +57,8 @@ class ImageManipulationWizard
     public function getWizardAction(ServerRequestInterface $request, ResponseInterface $response)
     {
         if ($this->isSignatureValid($request)) {
-            $queryParams = json_decode($request->getQueryParams()['arguments'], true);
-            $fileUid = $queryParams['image'];
+            $parsedBody = json_decode($request->getParsedBody()['arguments'], true);
+            $fileUid = $parsedBody['image'];
             $image = null;
             if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
                 try {
@@ -68,7 +68,7 @@ class ImageManipulationWizard
             }
             $viewData = [
                 'image' => $image,
-                'cropVariants' => $queryParams['cropVariants']
+                'cropVariants' => $parsedBody['cropVariants']
             ];
             $content = $this->templateView->renderSection('Main', $viewData);
             $response->getBody()->write($content);
@@ -81,12 +81,12 @@ class ImageManipulationWizard
     /**
      * Check if hmac signature is correct
      *
-     * @param ServerRequestInterface $request the request with the GET parameters
+     * @param ServerRequestInterface $request the request with the POST parameters
      * @return bool
      */
     protected function isSignatureValid(ServerRequestInterface $request)
     {
-        $token = GeneralUtility::hmac($request->getQueryParams()['arguments'], 'ajax_wizard_image_manipulation');
-        return hash_equals($token, $request->getQueryParams()['signature']);
+        $token = GeneralUtility::hmac($request->getParsedBody()['arguments'], 'ajax_wizard_image_manipulation');
+        return hash_equals($token, $request->getParsedBody()['signature']);
     }
 }
index f7deae7..28113b1 100644 (file)
@@ -14,6 +14,7 @@
                                                                        data-formengine-validation-rules="{formEngine.validation}"/>
                                                        <button class="btn btn-default t3js-image-manipulation-trigger"
                                                                        data-url="{wizardUri}"
+                                                                       data-payload="{wizardPayload}"
                                                                        data-preview-url="{previewUrl}"
                                                                        data-severity="notice"
                                                                        data-modal-title="{f:render(partial: 'ModalTitle', section:'Main', arguments: _all)}"
index 8dea2d0..74ac4fa 100644 (file)
  */
 
 /// <amd-dependency path='TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min' name='ImagesLoaded'>
+/// <amd-dependency path='TYPO3/CMS/Backend/Icons' name='Icons'>
 /// <amd-dependency path='TYPO3/CMS/Backend/Modal' name='Modal'>
 
 import $ = require('jquery');
 import 'jquery-ui/draggable';
 import 'jquery-ui/resizable';
+declare const Icons: any;
 declare const Modal: any;
 declare const ImagesLoaded: any;
 
@@ -230,55 +232,68 @@ class ImageManipulation {
     const buttonDismissText: string = this.trigger.data('buttonDismissText');
     const buttonSaveText: string = this.trigger.data('buttonSaveText');
     const imageUri: string = this.trigger.data('url');
+    const payload: object = this.trigger.data('payload');
     const initCropperModal: Function = this.initializeCropperModal.bind(this);
 
-    /**
-     * Open modal with image to crop
-     */
-    this.currentModal = Modal.advanced({
-      additionalCssClasses: ['modal-image-manipulation'],
-      ajaxCallback: initCropperModal,
-      buttons: [
-        {
-          btnClass: 'btn-default pull-left',
-          dataAttributes: {
+    Icons.getIcon(
+      'spinner-circle',
+      Icons.sizes.default,
+      null,
+      null,
+      Icons.markupIdentifiers.inline
+    ).done((icon: string): void => {
+      /**
+       * Open modal with image to crop
+       */
+      this.currentModal = Modal.advanced({
+        additionalCssClasses: ['modal-image-manipulation'],
+        buttons: [
+          {
+            btnClass: 'btn-default pull-left',
+            dataAttributes: {
               method: 'preview',
+            },
+            icon: 'actions-view',
+            text: buttonPreviewText,
           },
-          icon: 'actions-view',
-          text: buttonPreviewText,
-        },
-        {
-          btnClass: 'btn-default',
-          dataAttributes: {
+          {
+            btnClass: 'btn-default',
+            dataAttributes: {
               method: 'dismiss',
+            },
+            icon: 'actions-close',
+            text: buttonDismissText,
           },
-          icon: 'actions-close',
-          text: buttonDismissText,
-        },
-        {
-          btnClass: 'btn-primary',
-          dataAttributes: {
+          {
+            btnClass: 'btn-primary',
+            dataAttributes: {
               method: 'save',
+            },
+            icon: 'actions-document-save',
+            text: buttonSaveText,
           },
-          icon: 'actions-document-save',
-          text: buttonSaveText,
+        ],
+        callback: (currentModal: JQuery): void => {
+          $.post({
+            data: payload,
+            dataType: 'html',
+            url: imageUri,
+          }).done((response: string): void => {
+            initCropperModal();
+            currentModal.find('.t3js-modal-body').append(response).addClass('cropper');
+          });
         },
-      ],
-      callback: (currentModal: JQuery): void => {
-        currentModal.find('.t3js-modal-body')
-          .addClass('cropper');
-      },
-      content: imageUri,
-      size: Modal.sizes.full,
-      style: Modal.styles.dark,
-      title: modalTitle,
-      type: 'ajax',
-    });
-    this.currentModal.on('hide.bs.modal', (e: JQueryEventObject): void => {
-      this.destroy();
+        content: '<div class="modal-loading">' + icon + '</div>',
+        size: Modal.sizes.full,
+        style: Modal.styles.dark,
+        title: modalTitle,
+      });
+      this.currentModal.on('hide.bs.modal', (e: JQueryEventObject): void => {
+        this.destroy();
+      });
+      // Do not dismiss the modal when clicking beside it to avoid data loss
+      this.currentModal.data('bs.modal').options.backdrop = 'static';
     });
-    // Do not dismiss the modal when clicking beside it to avoid data loss
-    this.currentModal.data('bs.modal').options.backdrop = 'static';
   }
 
   /**
@@ -947,7 +962,9 @@ class ImageManipulation {
    */
   private destroy(): void {
     if (this.currentModal) {
-      this.cropper.cropper('destroy');
+      if (typeof this.cropper !== 'undefined' && this.cropper !== null) {
+        this.cropper.cropper('destroy');
+      }
       this.cropper = null;
       this.currentModal = null;
       this.data = null;
index 0cad2a6..b12a629 100644 (file)
@@ -10,7 +10,7 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require", "exports", "TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min", "TYPO3/CMS/Backend/Modal", "jquery", "jquery-ui/draggable", "jquery-ui/resizable"], function (require, exports, ImagesLoaded, Modal, $) {
+define(["require", "exports", "TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min", "TYPO3/CMS/Backend/Icons", "TYPO3/CMS/Backend/Modal", "jquery", "jquery-ui/draggable", "jquery-ui/resizable"], function (require, exports, ImagesLoaded, Icons, Modal, $) {
     "use strict";
     /**
      * Module: TYPO3/CMS/Backend/ImageManipulation
@@ -225,54 +225,61 @@ define(["require", "exports", "TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min", "T
             var buttonDismissText = this.trigger.data('buttonDismissText');
             var buttonSaveText = this.trigger.data('buttonSaveText');
             var imageUri = this.trigger.data('url');
+            var payload = this.trigger.data('payload');
             var initCropperModal = this.initializeCropperModal.bind(this);
-            /**
-             * Open modal with image to crop
-             */
-            this.currentModal = Modal.advanced({
-                additionalCssClasses: ['modal-image-manipulation'],
-                ajaxCallback: initCropperModal,
-                buttons: [
-                    {
-                        btnClass: 'btn-default pull-left',
-                        dataAttributes: {
-                            method: 'preview',
+            Icons.getIcon('spinner-circle', Icons.sizes.default, null, null, Icons.markupIdentifiers.inline).done(function (icon) {
+                /**
+                 * Open modal with image to crop
+                 */
+                _this.currentModal = Modal.advanced({
+                    additionalCssClasses: ['modal-image-manipulation'],
+                    buttons: [
+                        {
+                            btnClass: 'btn-default pull-left',
+                            dataAttributes: {
+                                method: 'preview',
+                            },
+                            icon: 'actions-view',
+                            text: buttonPreviewText,
                         },
-                        icon: 'actions-view',
-                        text: buttonPreviewText,
-                    },
-                    {
-                        btnClass: 'btn-default',
-                        dataAttributes: {
-                            method: 'dismiss',
+                        {
+                            btnClass: 'btn-default',
+                            dataAttributes: {
+                                method: 'dismiss',
+                            },
+                            icon: 'actions-close',
+                            text: buttonDismissText,
                         },
-                        icon: 'actions-close',
-                        text: buttonDismissText,
-                    },
-                    {
-                        btnClass: 'btn-primary',
-                        dataAttributes: {
-                            method: 'save',
+                        {
+                            btnClass: 'btn-primary',
+                            dataAttributes: {
+                                method: 'save',
+                            },
+                            icon: 'actions-document-save',
+                            text: buttonSaveText,
                         },
-                        icon: 'actions-document-save',
-                        text: buttonSaveText,
+                    ],
+                    callback: function (currentModal) {
+                        $.post({
+                            data: payload,
+                            dataType: 'html',
+                            url: imageUri,
+                        }).done(function (response) {
+                            initCropperModal();
+                            currentModal.find('.t3js-modal-body').append(response).addClass('cropper');
+                        });
                     },
-                ],
-                callback: function (currentModal) {
-                    currentModal.find('.t3js-modal-body')
-                        .addClass('cropper');
-                },
-                content: imageUri,
-                size: Modal.sizes.full,
-                style: Modal.styles.dark,
-                title: modalTitle,
-                type: 'ajax',
-            });
-            this.currentModal.on('hide.bs.modal', function (e) {
-                _this.destroy();
+                    content: '<div class="modal-loading">' + icon + '</div>',
+                    size: Modal.sizes.full,
+                    style: Modal.styles.dark,
+                    title: modalTitle,
+                });
+                _this.currentModal.on('hide.bs.modal', function (e) {
+                    _this.destroy();
+                });
+                // Do not dismiss the modal when clicking beside it to avoid data loss
+                _this.currentModal.data('bs.modal').options.backdrop = 'static';
             });
-            // Do not dismiss the modal when clicking beside it to avoid data loss
-            this.currentModal.data('bs.modal').options.backdrop = 'static';
         };
         /**
          * @method init
@@ -794,7 +801,9 @@ define(["require", "exports", "TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min", "T
          */
         ImageManipulation.prototype.destroy = function () {
             if (this.currentModal) {
-                this.cropper.cropper('destroy');
+                if (typeof this.cropper !== 'undefined' && this.cropper !== null) {
+                    this.cropper.cropper('destroy');
+                }
                 this.cropper = null;
                 this.currentModal = null;
                 this.data = null;