[FEATURE] Add TCA type image_manipulation 22/37622/22
authorFrans Saris <franssaris@gmail.com>
Sat, 7 Mar 2015 19:05:44 +0000 (20:05 +0100)
committerFrans Saris <franssaris@gmail.com>
Wed, 15 Apr 2015 09:38:23 +0000 (11:38 +0200)
TCA type image_manipulation brings a image manipulation wizard
to the core.

This first version brings image cropping with the possibility to
set a certain aspect ratio for the cropped area. The
sys_file_reference.crop property is extended and can now also hold
a json string to describe the image manipulation.

The LocalCropScaleMaskHelper that is used by the core
to create adjusted images is also adjusted to handle the new format.

Overriding TCA by TSConfig will be done in a followup.

Resolves: #65585
Releases: master
Change-Id: I58dee33e0f884ba2907259e8b03254c43f4c9186
Reviewed-on: http://review.typo3.org/37622
Reviewed-by: Benjamin Kott <info@bk2k.info>
Tested-by: Benjamin Kott <info@bk2k.info>
Reviewed-by: Andreas Fernandez <andreas.fernandez@aspedia.de>
Tested-by: Andreas Fernandez <andreas.fernandez@aspedia.de>
Reviewed-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Tested-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
25 files changed:
Build/Gruntfile.js
Build/bower.json
typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormEngine.php
typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php [new file with mode: 0644]
typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Classes/Page/PageRenderer.php
typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/TCA/sys_file_reference.php
typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst [new file with mode: 0644]
typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js [new file with mode: 0644]
typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js [new file with mode: 0644]
typo3/sysext/core/ext_tables.sql
typo3/sysext/lang/locallang_tca.xlf
typo3/sysext/lang/locallang_wizards.xlf
typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less [new file with mode: 0644]
typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_modal.less
typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
typo3/sysext/t3skin/Resources/Private/Styles/t3skin.less
typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png [new file with mode: 0644]

index 7b830f6..e6337c0 100644 (file)
@@ -49,6 +49,8 @@ module.exports = function(grunt) {
                                        '<%= paths.core %>Public/JavaScript/Contrib/jquery.dataTables.js': '/datatables/media/js/jquery.dataTables.min.js',
                                        '<%= paths.core %>Public/JavaScript/Contrib/require.js': '/requirejs/require.js',
                                        '<%= paths.core %>Public/JavaScript/Contrib/moment.js': '/moment/moment.js',
+                                       '<%= paths.core %>Public/JavaScript/Contrib/cropper.min.js': '/cropper/dist/cropper.min.js',
+                                       '<%= paths.core %>Public/JavaScript/Contrib/imagesloaded.pkgd.min.js': '/imagesloaded/imagesloaded.pkgd.min.js',
                                        '<%= paths.core %>Public/JavaScript/Contrib/bootstrap-datetimepicker.js': '/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js',
                                        '<%= paths.core %>Public/JavaScript/Contrib/autosize.js': '/autosize/dest/autosize.min.js',
                                        '<%= paths.core %>Public/JavaScript/Contrib/placeholders.jquery.min.js': '/Placeholders.js/dist/placeholders.jquery.min.js',
index 4cccb51..b1a966c 100644 (file)
@@ -35,6 +35,8 @@
     "nprogress": "0.1.6",
     "datatables": "1.10.5",
     "autosize": "2.0.0",
+    "cropper": "0.9.1",
+    "imagesloaded": "3.1.8",
     "Placeholders.js": "4.0.1"
   }
 }
diff --git a/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php b/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
new file mode 100644 (file)
index 0000000..1d2455d
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Form\FormEngine;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Extbase\Utility\ArrayUtility;
+use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
+
+/**
+ * Generation of crop image TCEform elements
+ */
+class ImageManipulationElement extends AbstractFormElement {
+
+       /**
+        * Default element configuration
+        *
+        * @var array
+        */
+       protected $defaultConfig = array(
+               'file_field' => 'uid_local',
+               'enableZoom' => FALSE,
+               'allowedExtensions' => NULL, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
+               'ratios' => array(
+                       '1.7777777777777777' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.16_9',
+                       '1.3333333333333333' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.4_3',
+                       '1' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.1_1',
+                       'NaN' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.free',
+               )
+       );
+
+       /**
+        * Handler for unknown types.
+        *
+        * @param string $table The table name of the record
+        * @param string $field The field name which this element is supposed to edit
+        * @param array $row The record data array where the value(s) for the field can be found
+        * @param array $additionalInformation An array with additional configuration options.
+        * @return string The HTML code for the TCEform field
+        */
+       public function render($table, $field, $row, &$additionalInformation) {
+               // If ratios are set do not add default options
+               if (isset($additionalInformation['fieldConf']['config']['ratios'])) {
+                       unset($this->defaultConfig['ratios']);
+               }
+               $config = ArrayUtility::arrayMergeRecursiveOverrule($this->defaultConfig, $additionalInformation['fieldConf']['config']);
+
+               // By default we allow all image extensions that can be handled by the GFX functionality
+               if ($config['allowedExtensions'] === NULL) {
+                       $config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
+               }
+
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $formEngineDummy = new FormEngine();
+                       $noneElement = GeneralUtility::makeInstance(NoneElement::class, $formEngineDummy);
+                       $elementConfiguration = array(
+                               'fieldConf' => array(
+                                       'config' => $config,
+                               ),
+                               'itemFormElValue' => $additionalInformation['itemFormElValue'],
+                       );
+                       return $noneElement->render('', '', '', $elementConfiguration);
+               }
+
+               $file = $this->getFile($row, $config['file_field']);
+               if (!$file) {
+                       return '';
+               }
+
+               $content = '';
+               $preview = '';
+               if (GeneralUtility::inList(mb_strtolower($config['allowedExtensions']), mb_strtolower($file->getExtension()))) {
+
+                       // Get preview
+                       $preview = $this->getPreview($file, $additionalInformation['itemFormElValue']);
+
+                       // Check if ratio labels hold translation strings
+                       $languageService = $this->getLanguageService();
+                       foreach ((array)$config['ratios'] as $ratio => $label) {
+                               $config['ratios'][$ratio] = $languageService->sL($label, TRUE);
+                       }
+
+                       $formFieldId = str_replace('.', '', uniqid('formengine-image-manipulation-', TRUE));
+                       $wizardData = array(
+                               'file' => $file->getUid(),
+                               'zoom' => $config['enableZoom'] ? '1' : '0',
+                               'ratios' => json_encode($config['ratios']),
+                       );
+                       $wizardData['token'] = GeneralUtility::hmac(implode('|', $wizardData), 'ImageManipulationWizard');
+
+                       $buttonAttributes = array(
+                               'data-url' => BackendUtility::getAjaxUrl('ImageManipulationWizard::getHtmlForImageManipulationWizard', $wizardData),
+                               'data-severity' => 'notice',
+                               'data-image-name' => $file->getNameWithoutExtension(),
+                               'data-image-uid' => $file->getUid(),
+                               'data-file-field' => $config['file_field'],
+                               'data-field' => $formFieldId,
+                       );
+
+                       $button = '<button class="btn btn-default t3js-image-manipulation-trigger"';
+                       foreach ($buttonAttributes as $key => $value) {
+                               $button .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
+                       }
+                       $button .= '><span class="t3-icon fa fa-crop"></span>';
+                       $button .= $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.open-editor', TRUE);
+                       $button .= '</button>';
+
+                       $inputField = '<input type="hidden" '
+                               . 'id="' . $formFieldId . '" '
+                               . 'name="' . $additionalInformation['itemFormElName'] . '" '
+                               . 'value="' . htmlspecialchars($additionalInformation['itemFormElValue']) . '" />';
+
+                       $content .= $inputField . $button;
+
+                       $content .= $this->getImageManipulationInfoTable($additionalInformation['itemFormElValue']);
+
+                       /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
+                       $pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
+                       $pageRenderer->loadRequireJsModule(
+                               'TYPO3/CMS/Backend/ImageManipulation',
+                               'function(ImageManipulation){ImageManipulation.initializeTrigger()}' // Initialize after load
+                       );
+               }
+
+               $content .= '<p class="text-muted"><em>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.supported-types-message', TRUE) . '<br />';
+               $content .= mb_strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'])));
+               $content .= '</em></p>';
+
+               $item = '<div class="media">';
+               $item .= $preview;
+               $item .= '<div class="media-body">' . $content . '</div>';
+               $item .= '</div>';
+
+               return $item;
+       }
+
+       /**
+        * Get file object
+        *
+        * @param array $row
+        * @param string $fieldName
+        * @return NULL|\TYPO3\CMS\Core\Resource\File
+        */
+       protected function getFile(array $row, $fieldName) {
+               $file = NULL;
+               $fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : NULL;
+
+               if (strpos($fileUid, 'sys_file_') === 0) {
+                       $fileUid = substr($fileUid, 9);
+               }
+               if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
+                       try {
+                               $file = ResourceFactory::getInstance()->getFileObject($fileUid);
+                       } catch (FileDoesNotExistException $e) {
+                       }
+               }
+               return $file;
+       }
+
+       /**
+        * Get preview image if cropping is set
+        *
+        * @param File $file
+        * @param string $crop
+        * @return string
+        */
+       public function getPreview(File $file, $crop) {
+               $preview = '';
+               if ($crop) {
+                       $imageSetup = array('width' => '150m', 'height' => '200m', 'crop' => $crop);
+                       $processedImage = $file->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
+                       // Only use a thumbnail if the processing process was successful by checking if image width is set
+                       if ($processedImage->getProperty('width')) {
+                               $imageUrl = $processedImage->getPublicUrl(TRUE);
+                               $preview = '<img src="' . $imageUrl . '" ' .
+                                       'class="media-object thumbnail" ' .
+                                       'width="' . $processedImage->getProperty('width') . '" ' .
+                                       'height="' . $processedImage->getProperty('height') . '" >';
+                       }
+               }
+               return '<div class="media-left t3js-image-manipulation-preview' . ($preview ? '' : ' hide'). '">' . $preview . '</div>';
+       }
+
+       /**
+        * Get image manipulation info table
+        *
+        * @param string $rawImageManipulationValue
+        * @return string
+        */
+       protected function getImageManipulationInfoTable($rawImageManipulationValue) {
+               $content = '';
+               $imageManipulation = NULL;
+               $x = $y = $width = $height = 0;
+
+               // Determine cropping values
+               if ($rawImageManipulationValue) {
+                       $imageManipulation = json_decode($rawImageManipulationValue);
+                       if (is_object($imageManipulation)) {
+                               $x = (int)$imageManipulation->x;
+                               $y = (int)$imageManipulation->y;
+                               $width = (int)$imageManipulation->width;
+                               $height = (int)$imageManipulation->height;
+                       } else {
+                               $imageManipulation = NULL;
+                       }
+               }
+               $languageService = $this->getLanguageService();
+
+               $content .= '<div class="table-fit-block table-spacer-wrap">';
+               $content .= '<table class="table table-no-borders t3js-image-manipulation-info'. ($imageManipulation === NULL ? ' hide' : '') . '">';
+               $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x', TRUE) . '</td>';
+               $content .= '<td class="t3js-image-manipulation-info-crop-x">' . $x . 'px</td></tr>';
+               $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y', TRUE) . '</td>';
+               $content .= '<td class="t3js-image-manipulation-info-crop-y">' . $y . 'px</td></tr>';
+               $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width', TRUE) . '</td>';
+               $content .= '<td class="t3js-image-manipulation-info-crop-width">' . $width . 'px</td></tr>';
+               $content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height', TRUE) . '</td>';
+               $content .= '<td class="t3js-image-manipulation-info-crop-height">' . $height . 'px</td></tr>';
+               $content .= '</table>';
+               $content .= '</div>';
+
+               return $content;
+       }
+}
index 68ccf58..892f129 100644 (file)
@@ -1039,6 +1039,7 @@ class FormEngine {
                                'none' => 'NoneElement',
                                'user' => 'UserElement',
                                'flex' => 'FlexElement',
+                               'image_manipulation' => 'ImageManipulationElement',
                                'unknown' => 'UnknownElement',
                        );
                        if (!isset($typeClassNameMapping[$type])) {
diff --git a/typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php b/typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php
new file mode 100644 (file)
index 0000000..3847fd7
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Wizard;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Wizard for rendering image manipulation view
+ */
+class ImageManipulationWizard {
+
+       /**
+        * @var string
+        */
+       protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
+
+       /**
+        * Returns the html for the AJAX API
+        *
+        * @param array $params
+        * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxRequestHandler
+        * @return void
+        */
+       public function getHtmlForImageManipulationWizard($params, $ajaxRequestHandler) {
+               if (!$this->checkHmacToken()) {
+                       HttpUtility::setResponseCodeAndExit(HttpUtility::HTTP_STATUS_403);
+               }
+
+               $fileUid = GeneralUtility::_GET('file');
+               $image = NULL;
+               if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
+                       try {
+                               $image = ResourceFactory::getInstance()->getFileObject($fileUid);
+                       } catch (FileDoesNotExistException $e) {}
+               }
+
+               $view = $this->getFluidTemplateObject($this->templatePath . 'Wizards/ImageManipulationWizard.html');
+               $view->assign('image', $image);
+               $view->assign('zoom', (bool)GeneralUtility::_GET('zoom'));
+               $view->assign('ratios', $this->getRatiosArray());
+               $content = $view->render();
+
+               $ajaxRequestHandler->addContent('content', $content);
+               $ajaxRequestHandler->setContentFormat('html');
+       }
+
+       /**
+        * Check if hmac token is correct
+        *
+        * @return bool
+        */
+       protected function checkHmacToken() {
+               $parameters = array();
+               if (GeneralUtility::_GET('file')) {
+                       $parameters['file'] = GeneralUtility::_GET('file');
+               }
+               $parameters['zoom'] = GeneralUtility::_GET('zoom') ? '1' : '0';
+               $parameters['ratios'] = GeneralUtility::_GET('ratios') ?: '';
+
+               $token = GeneralUtility::hmac(implode('|', $parameters), 'ImageManipulationWizard');
+               return $token === GeneralUtility::_GET('token');
+       }
+
+       /**
+        * Get available ratios
+        *
+        * @return array
+        */
+       protected function getRatiosArray() {
+               $ratios = json_decode(GeneralUtility::_GET('ratios'));
+               // Json transforms a array with sting keys to a array,
+               // we need to transform this to an array for the fluid ForViewHelper
+               if (is_object($ratios)) {
+                       $ratios = get_object_vars($ratios);
+               }
+               return $ratios;
+       }
+
+       /**
+        * Returns a new standalone view, shorthand function
+        *
+        * @param string $templatePathAndFileName optional the path to set the template path and filename
+        * @return StandaloneView
+        */
+       protected function getFluidTemplateObject($templatePathAndFileName = NULL) {
+               $view = GeneralUtility::makeInstance(StandaloneView::class);
+               if ($templatePathAndFileName) {
+                       $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
+               }
+               return $view;
+       }
+}
diff --git a/typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html b/typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html
new file mode 100644 (file)
index 0000000..2d3d92f
--- /dev/null
@@ -0,0 +1,102 @@
+
+<f:if condition="{image.properties.width}">
+       <f:then>
+               <div class="modal-panel">
+
+                       <div class="modal-panel-sidebar modal-panel-sidebar-right">
+                               <div class="modal-header">
+                                       <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+                                       <h4 class="modal-title"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.image-manipulation" /></h4>
+                               </div>
+                               <div class="modal-body">
+                                       <form class="form">
+                                               <div class="form-group">
+                                                       <label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.image-title" />:</label>
+                                                       <p>{f:if(condition:image.properties.title, then:image.properties.title, else:image.name)}</p>
+                                               </div>
+                                               <div class="form-group">
+                                                       <label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.original-dimensions" />:</label>
+                                                       <p>{image.properties.width} × {image.properties.height}</p>
+                                               </div>
+
+                                               <f:if condition="{ratios}">
+                                               <div class="form-group">
+                                                       <label for="ratio"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.aspect-ratio" /></label>
+                                                       <div class="btn-group btn-group-justified t3js-ratio-buttons" data-toggle="buttons">
+                                                               <f:for each="{ratios}" as="ratio" key="key" iteration="iteration">
+                                                                       <label class="btn btn-default" data-method="setAspectRatio" data-option="{key}" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.set-aspect-ratio')}"><input class="sr-only" id="aspestRatio{iteration.cycle}" name="aspestRatio" value="{key}" type="radio"> <span>{ratio}</span></label>
+                                                               </f:for>
+                                                       </div>
+                                               </div>
+                                               </f:if>
+
+                                               <f:if condition="{zoom}">
+                                               <div class="form-group t3js-setting-zoom">
+                                                       <label for="zoom"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom" /></label>
+                                                       <div class="btn-group">
+                                                               <button class="btn btn-default" data-method="zoom" data-option="0.1" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom-in')}"><i class="fa fa-search-plus"></i></button>
+                                                               <button class="btn btn-default" data-method="zoom" data-option="-0.1" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom-out')}"><i class="fa fa-search-minus"></i></button>
+                                                       </div>
+                                               </div>
+                                               </f:if>
+
+                                               <div class="form-group">
+                                                       <label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.selection" /></label>
+                                                       <div class="table-fit-block">
+                                                               <table class="table table-no-borders table-transparent t3js-image-manipulation-info">
+                                                                       <tr>
+                                                                               <td>
+                                                                                       <f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x"/>
+                                                                               </td>
+                                                                               <td class="t3js-image-manipulation-info-crop-x"></td>
+                                                                       </tr>
+                                                                       <tr>
+                                                                               <td>
+                                                                                       <f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y"/>
+                                                                               </td>
+                                                                               <td class="t3js-image-manipulation-info-crop-y"></td>
+                                                                       </tr>
+                                                                       <tr>
+                                                                               <td>
+                                                                                       <f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width"/>
+                                                                               </td>
+                                                                               <td class="t3js-image-manipulation-info-crop-width"></td>
+                                                                       </tr>
+                                                                       <tr>
+                                                                               <td>
+                                                                                       <f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height"/>
+                                                                               </td>
+                                                                               <td class="t3js-image-manipulation-info-crop-height"></td>
+                                                                       </tr>
+                                                               </table>
+                                                       </div>
+                                                       <div class="form-group">
+                                                               <button class="btn btn-default" data-method="reset" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.reset')}"><i class="fa fa-refresh"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.reset')}</button>
+                                                       </div>
+                                               </div>
+
+                                       </form>
+                               </div>
+                               <div class="modal-footer">
+                                       <button class="btn btn-default" data-method="dismiss" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.cancel')}"><i class="fa fa-remove"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.cancel')}</button>
+                                       <button class="btn btn-default" data-method="save" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.accept')}"><i class="fa fa-check"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.accept')}</button>
+                               </div>
+                       </div>
+
+                       <div class="modal-panel-body">
+                               <div class="t3js-cropper-image-container">
+                                       <img src="{f:uri.image(image:image, maxWidth:'1600', maxHeight: '1024')}"
+                                                naturalWidth="{image.properties.width}" naturalHeight="{image.properties.height}" />
+                               </div>
+                       </div>
+
+               </div>
+
+       </f:then>
+       <f:else>
+               <div class="alert alert-danger">
+                       <h4 class="alert-title"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.no-image-found" /></h4>
+                       <p class="alert-message"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.no-image-found-message" /></p>
+               </div>
+       </f:else>
+</f:if>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js b/typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js
new file mode 100644 (file)
index 0000000..15392fd
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * contains all logic for the image crop GUI
+ */
+define('TYPO3/CMS/Backend/ImageManipulation', ['jquery', 'TYPO3/CMS/Backend/Modal'], function ($) {
+
+       var ImageManipulation = {
+               margin: 20,
+               currentModal: null,
+               cropperSelector: '.t3js-cropper-image-container > img',
+               $trigger: null
+       };
+
+
+       /**
+        * Initialize triggers
+        */
+       ImageManipulation.initializeTrigger = function() {
+               var $triggers = $('.t3js-image-manipulation-trigger');
+               // Remove existing bind function
+               $triggers.off('click', ImageManipulation.buttonClick);
+               // Bind new function
+               $triggers.on('click', ImageManipulation.buttonClick);
+       };
+
+       /**
+        * Functions that should be bind to the trigger button
+        *
+        * @param e click event
+        */
+       ImageManipulation.buttonClick = function(e) {
+               e.preventDefault();
+               // Prevent double trigger
+               if (ImageManipulation.$trigger !== $(this)) {
+                       ImageManipulation.$trigger = $(this);
+                       ImageManipulation.show();
+               }
+       };
+
+       /**
+        * Open modal with image to crop
+        */
+       ImageManipulation.show = function() {
+               ImageManipulation.currentModal = top.TYPO3.Modal.loadUrl(
+                       ImageManipulation.$trigger.data('image-name'),
+                       TYPO3.Severity.notice,
+                       [],
+                       ImageManipulation.$trigger.data('url'),
+                       ImageManipulation.initializeCropperModal,
+                       '.modal-content'
+               );
+               ImageManipulation.currentModal.addClass('modal-dark');
+       };
+
+       /**
+        * Initialize the cropper modal
+        */
+       ImageManipulation.initializeCropperModal = function() {
+               top.require(['cropper', 'imagesloaded'], function(cropperJs, imagesLoaded) {
+                       var $image = ImageManipulation.getCropper();
+
+                       // wait until image is loaded
+                       imagesLoaded($image, function() {
+                               var $modal = ImageManipulation.currentModal.find('.modal-dialog');
+                               var $modalContent = $modal.find('.modal-content');
+                               var $modalPanelSidebar = $modal.find('.modal-panel-sidebar');
+                               var $modalPanelBody = $modal.find('.modal-panel-body');
+                               // Let modal auto-fill width
+                               $modal.css({width:'auto', marginLeft: ImageManipulation.margin, marginRight: ImageManipulation.margin})
+                                         .addClass('modal-image-manipulation modal-resize');
+
+                               $modalContent.addClass('cropper-bg');
+
+                               // Determine available height
+                               var height = top.TYPO3.jQuery(window).height()
+                                               - (ImageManipulation.margin * 2);
+                               $image.css({maxHeight: height});
+
+                               // Wait a few microseconds before calculating available width (DOM isn't always updated direct)
+                               setTimeout(function() {
+                                       $modalPanelBody.css({width: $modalContent.innerWidth() - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 2)});
+
+                                       setTimeout(function() {
+                                               // Shrink modal when possible (the set left/right margin + width auto above makes it fill 100%)
+                                               var minWidth = Math.max(500, $image.outerWidth() + $modalPanelSidebar.outerWidth() + (ImageManipulation.margin * 2));
+                                               var width = $modal.width() > minWidth ? minWidth : $modal.width();
+                                               $modal.width(width);
+                                               $modalPanelBody.width(width - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 2));
+
+                                               var modalBodyMinHeight = $modalContent.height() -
+                                                       ($modalPanelSidebar.find('.modal-header').outerHeight() + $modalPanelSidebar.find('.modal-body-footer').outerHeight());
+                                               $modalPanelSidebar.find('.modal-body').css('min-height', modalBodyMinHeight);
+
+                                               // Center modal horizontal
+                                               $modal.css({marginLeft: 'auto', marginRight: 'auto'});
+
+                                               // Center modal vertical
+                                               top.TYPO3.Modal.center();
+
+                                               // Wait a few microseconds to let the modal resize
+                                               setTimeout(ImageManipulation.initializeCropper, 100);
+                                       }, 100);
+
+                               }, 100);
+                       });
+
+               });
+       };
+
+       /**
+        * Initialize cropper
+        */
+       ImageManipulation.initializeCropper = function() {
+               var $image = ImageManipulation.getCropper(), cropData;
+
+               // Give img-container same dimensions as the image
+               ImageManipulation.currentModal.find('.t3js-cropper-image-container').
+               css({width: $image.width(), height: $image.height()});
+
+               var $trigger = ImageManipulation.$trigger;
+               var jsonString = $trigger.parent().find('#' + $trigger.data('field')).val();
+               if (jsonString.length) {
+                       cropData = $.parseJSON(jsonString);
+               }
+
+               // Calculate current resize factor
+               var factor = $image.width() / $image.prop('naturalWidth');
+               var $infoX = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-x');
+               var $infoY = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-y');
+               var $infoWidth = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-width');
+               var $infoHeight = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-height');
+
+               $image.cropper({
+                       autoCropArea: 0.5,
+                       strict: false,
+                       zoomable: ImageManipulation.currentModal.find('.t3js-setting-zoom').length > 0,
+                       built: function() {
+                               if (cropData) {
+                                       var cropBox = {};
+                                       cropBox.left = cropData.x * factor;
+                                       cropBox.top = cropData.y * factor;
+                                       cropBox.width = cropData.width * factor;
+                                       cropBox.height = cropData.height * factor;
+                                       $image.cropper('setCropBoxData', cropBox);
+                               }
+                       },
+                       crop: function (data) {
+                               $infoX.text(Math.round(data.x) + 'px');
+                               $infoY.text(Math.round(data.y) + 'px');
+                               $infoWidth.text(Math.round(data.height) + 'px');
+                               $infoHeight.text(Math.round(data.width) + 'px');
+                       }
+               });
+
+               // Destroy cropper when modal is closed
+               ImageManipulation.currentModal.on('hidden.bs.modal', function() {
+                       $image.cropper('destroy');
+               });
+
+               ImageManipulation.initializeCroppingActions();
+       };
+
+       /**
+        * Get image to be cropped
+        *
+        * @returns jQuery object
+        */
+       ImageManipulation.getCropper = function() {
+               return ImageManipulation.currentModal.find(ImageManipulation.cropperSelector);
+       };
+
+       /**
+        * Bind buttons from cropper tool panel
+        */
+       ImageManipulation.initializeCroppingActions = function() {
+               ImageManipulation.currentModal.find('[data-method]').click(function(e) {
+                       e.preventDefault();
+                       var method = $(this).data('method');
+                       var options = $(this).data('option') || {};
+                       if (typeof ImageManipulation[method] === 'function') {
+                               ImageManipulation[method](options);
+                       }
+               });
+       };
+
+       /**
+        * Change the aspect ratio of the crop box
+        *
+        * @param aspectRatio Number
+        */
+       ImageManipulation.setAspectRatio = function(aspectRatio) {
+               var $cropper = ImageManipulation.getCropper();
+               $cropper.cropper('setAspectRatio', aspectRatio);
+       };
+
+       /**
+        * Set zoom ratio
+        *
+        * Zoom in: requires a positive number (ratio > 0)
+        * Zoom out: requires a negative number (ratio < 0)
+        *
+        * @param ratio Number
+        */
+       ImageManipulation.zoom = function(ratio) {
+               var $cropper = ImageManipulation.getCropper();
+               $cropper.cropper('zoom', ratio);
+       };
+
+       /**
+        * Save crop values in form and close modal
+        */
+       ImageManipulation.save = function() {
+               var $image = ImageManipulation.getCropper();
+               var $trigger = ImageManipulation.$trigger;
+               var formFieldId = $trigger.data('field');
+               var $formField = $trigger.parent().find('#' + formFieldId);
+               var $formGroup = $formField.closest('.form-group');
+               var cropData = $image.cropper('getData');
+               var newValue = '';
+               $formGroup.addClass('has-change');
+               if (cropData.width > 0 && cropData.height > 0) {
+                       newValue = JSON.stringify(cropData);
+                       $formGroup.find('.t3js-image-manipulation-info').removeClass('hide');
+                       $formGroup.find('.t3js-image-manipulation-info-crop-x').text(Math.round(cropData.x) + 'px');
+                       $formGroup.find('.t3js-image-manipulation-info-crop-y').text(Math.round(cropData.y) + 'px');
+                       $formGroup.find('.t3js-image-manipulation-info-crop-width').text(Math.round(cropData.width) + 'px');
+                       $formGroup.find('.t3js-image-manipulation-info-crop-height').text(Math.round(cropData.height) + 'px');
+
+               } else {
+                       $formGroup.find('.t3js-image-manipulation-info').addClass('hide');
+                       $formGroup.find('.t3js-image-manipulation-preview').addClass('hide');
+               }
+               $formField.val(newValue);
+               ImageManipulation.dismiss();
+       };
+
+       /**
+        * Reset crop selection
+        */
+       ImageManipulation.reset = function() {
+               var $image = ImageManipulation.getCropper();
+               $image.cropper('clear');
+       };
+
+       /**
+        * Close the current open modal
+        */
+       ImageManipulation.dismiss = function() {
+               if (ImageManipulation.currentModal) {
+                       ImageManipulation.currentModal.modal('hide').remove();
+                       ImageManipulation.currentModal = null;
+               }
+       };
+
+       return function() {
+               TYPO3.ImageManipulation = ImageManipulation;
+               return ImageManipulation;
+       }();
+});
index 2ba2f2a..cfdbae8 100644 (file)
@@ -112,6 +112,29 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
        };
 
        /**
+        * load URL with AJAX, append the content to the modal-body
+        * and trigger the callback
+        *
+        * @param {string} title
+        * @param {int} severity
+        * @param {array} buttons
+        * @param {string} url
+        * @param {string} target
+        * @param {function} callback
+        */
+       Modal.loadUrl = function(title, severity, buttons, url, callback, target) {
+               $.get(url, function(response) {
+                       Modal.currentModal.find(target ? target : '.modal-body').empty().append(response);
+                       if (callback) {
+                               callback();
+                       }
+                       Modal.currentModal.trigger('modal-loaded');
+               }, 'html');
+               return Modal.show(title, '<p class="loadmessage"><i class="fa fa-spinner fa-spin fa-5x "></i></p>', severity, buttons);
+       };
+
+
+       /**
         * Shows a dialog
         * Events:
         * - button.clicked
@@ -233,6 +256,7 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
                $(document).on('click', '.t3js-modal-trigger', function(evt) {
                        evt.preventDefault();
                        var $element = $(this);
+                       var url = $element.data('url') || null;
                        var title = $element.data('title') || 'Alert';
                        var content = $element.data('content') || 'Are you sure?';
                        var severity = (typeof top.TYPO3.Severity[$element.data('severity')] !== 'undefined') ? top.TYPO3.Severity[$element.data('severity')] : top.TYPO3.Severity.info;
@@ -252,7 +276,13 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
                                        }
                                }
                        ];
-                       Modal.confirm(title, content, severity, buttons);
+                       if (url !== null) {
+                               var separator = (url.indexOf('?') > -1) ? '&' : '?';
+                               var params = $.param({data: $element.data()});
+                               Modal.loadUrl(title, severity, buttons, url + separator + params);
+                       } else {
+                               Modal.show(title, content, severity, buttons);
+                       }
                });
        };
 
index b3b4f36..a305d2c 100644 (file)
@@ -1598,7 +1598,7 @@ class DataHandler {
                                $res = $this->checkValue_text($res, $value, $tcaFieldConf, $PP, $field);
                                break;
                        case 'passthrough':
-
+                       case 'image_manipulation':
                        case 'user':
                                $res['value'] = $value;
                                break;
@@ -1612,7 +1612,6 @@ class DataHandler {
                                $res = $this->checkValue_radio($res, $value, $tcaFieldConf, $PP);
                                break;
                        case 'group':
-
                        case 'select':
                                $res = $this->checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field);
                                break;
index 692b5bb..865c0b3 100644 (file)
@@ -1594,6 +1594,8 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface {
                                'datatables' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/jquery.dataTables',
                                'nprogress' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/nprogress',
                                'moment' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/moment',
+                               'cropper' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/cropper.min',
+                               'imagesloaded' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min',
                                'bootstrap' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap',
                                'twbs/bootstrap-datetimepicker' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/bootstrap-datetimepicker',
                                'autosize' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/autosize',
index 11e687b..91675a5 100644 (file)
@@ -76,9 +76,18 @@ class LocalCropScaleMaskHelper {
 
                $croppedImage = NULL;
                if (!empty($configuration['crop'])) {
+
+                       // check if it is a json object
+                       $cropData = json_decode($configuration['crop']);
+                       if ($cropData) {
+                               $crop = implode(',', array((int)$cropData->x, (int)$cropData->y, (int)$cropData->width, (int)$cropData->height));
+                       } else {
+                               $crop = $configuration['crop'];
+                       }
+
                        $im = $gifBuilder->imageCreateFromFile($originalFileName);
                        $croppedImage = Utility\GeneralUtility::tempnam('crop_', '.' . $sourceFile->getExtension());
-                       $gifBuilder->crop($im, ['crop' => $configuration['crop']]);
+                       $gifBuilder->crop($im, ['crop' => $crop]);
                        if ($gifBuilder->ImageWrite($im, $croppedImage)) {
                                $originalFileName = $croppedImage;
                        }
index 37b5fbd..0cfc56f 100644 (file)
@@ -735,6 +735,10 @@ return array(
                        'UserSettings::process' => array(
                                'callbackMethod' => \TYPO3\CMS\Backend\Controller\UserSettingsController::class . '->processAjaxRequest',
                                'csrfTokenCheck' => TRUE
+                       ),
+                       'ImageManipulationWizard::getHtmlForImageManipulationWizard' => array(
+                               'callbackMethod' => \TYPO3\CMS\Backend\Form\Wizard\ImageManipulationWizard::class . '->getHtmlForImageManipulationWizard',
+                               'csrfTokenCheck' => TRUE
                        )
                ),
                'toolbarItems' => array(), // Array: Registered toolbar items classes
index 9499f0c..cde3028 100644 (file)
@@ -221,10 +221,7 @@ return array(
                        'exclude' => 1,
                        'label' => 'LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.crop',
                        'config' => array(
-                               'type' => 'input',
-                               'size' => '10',
-                               'max' => '30',
-                               'placeholder' => 'x,y,w,h',
+                               'type' => 'image_manipulation'
                        )
                )
        ),
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst
new file mode 100644 (file)
index 0000000..02ea061
--- /dev/null
@@ -0,0 +1,37 @@
+=================================================
+Feature - #65585: Add TCA type image_manipulation
+=================================================
+
+Description
+===========
+
+TCA type `image_manipulation` brings a image manipulation wizard to the core.
+
+This first version brings image cropping with the possibility to
+set a certain aspect ratio for the cropped area. The
+sys_file_reference.crop property is extended and can now also hold
+a json string to describe the image manipulation.
+
+The `LocalCropScaleMaskHelper` that is used by the core
+to create adjusted images is also adjusted to handle the new format.
+
+
+Impact
+======
+
+There is an new TCA type column type `image_manipulation` it supports the following config:
+
+- file_field: string, default `uid_local`
+- enableZoom: bool, default `FALSE`
+- allowedExtensions: string, default `$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']`
+- ratios: array, default
+    '1.7777777777777777' => '16:9',
+    '1.3333333333333333' => '4:3',
+    '1' => '1:1',
+    'NaN' => 'Free',
+
+When `ratios` is set in TCA the defaults are neglected.
+
+
+Property `sys_file_reference.crop` can now hold a string representing a json object. `LocalCropScaleMaskHelper` checks
+if the it can parse the string as json. If it can it assumes it holds the properties: `x`, `y`, `width` and `height`.
\ No newline at end of file
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js
new file mode 100644 (file)
index 0000000..cb95024
--- /dev/null
@@ -0,0 +1,10 @@
+/*!
+ * Cropper v0.9.1
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2015 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2015-03-21T04:58:27.265Z
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){"use strict";function b(a){return"number"==typeof a}function c(a){return"undefined"==typeof a}function d(a,c){var d=[];return b(c)&&d.push(c),d.slice.apply(a,d)}function e(a,b){var c=d(arguments,2);return function(){return a.apply(b,c.concat(d(arguments)))}}function f(a){var b=a.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);return b&&(b[1]!==n.protocol||b[2]!==n.hostname||b[3]!==n.port)}function g(a){var b="timestamp="+(new Date).getTime();return a+(-1===a.indexOf("?")?"?":"&")+b}function h(a){return a?"rotate("+a+"deg)":"none"}function i(a,b){var c,d,e=P(a.degree)%180,f=(e>90?180-e:e)*Math.PI/180,g=Q(f),h=R(f),i=a.width,j=a.height,k=a.aspectRatio;return b?(c=i/(h+g/k),d=c/k):(c=i*h+j*g,d=i*g+j*h),{width:c,height:d}}function j(b,c){var d=a("<canvas>")[0],e=d.getContext("2d"),f=c.naturalWidth,g=c.naturalHeight,h=c.rotate,j=i({width:f,height:g,degree:h});return h?(d.width=j.width,d.height=j.height,e.save(),e.translate(j.width/2,j.height/2),e.rotate(h*Math.PI/180),e.drawImage(b,-f/2,-g/2,f,g),e.restore()):(d.width=f,d.height=g,e.drawImage(b,0,0,f,g)),d}function k(b,c){this.$element=a(b),this.options=a.extend({},k.DEFAULTS,a.isPlainObject(c)&&c),this.ready=!1,this.built=!1,this.rotated=!1,this.cropped=!1,this.disabled=!1,this.load()}var l=a(window),m=a(document),n=window.location,o=".cropper",p=/^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/,q="cropper-modal",r="cropper-hide",s="cropper-hidden",t="cropper-invisible",u="cropper-move",v="cropper-crop",w="cropper-disabled",x="cropper-bg",y="mousedown touchstart",z="mousemove touchmove",A="mouseup mouseleave touchend touchleave touchcancel",B="wheel mousewheel DOMMouseScroll",C="dblclick",D="resize"+o,E="build"+o,F="built"+o,G="dragstart"+o,H="dragmove"+o,I="dragend"+o,J="zoomin"+o,K="zoomout"+o,L=a.isFunction(a("<canvas>")[0].getContext),M=Math.sqrt,N=Math.min,O=Math.max,P=Math.abs,Q=Math.sin,R=Math.cos,S=parseFloat,T={};T.load=function(b){var c,d,e,h=this.options,i=this.$element;b||(i.is("img")?b=i.prop("src"):i.is("canvas")&&L&&(b=i[0].toDataURL())),b&&(d=a.Event(E),i.one(E,h.build).trigger(d),d.isDefaultPrevented()||(h.checkImageOrigin&&f(b)&&(c=" crossOrigin",i.prop("crossOrigin")||(b=g(b))),this.$clone=e=a("<img>"),e.one("load",a.proxy(function(){var a=e.prop("naturalWidth")||e.width(),c=e.prop("naturalHeight")||e.height();this.image={naturalWidth:a,naturalHeight:c,aspectRatio:a/c,rotate:0},this.url=b,this.ready=!0,this.build()},this)).attr({src:b,crossOrigin:c}),e.addClass(r).insertAfter(i)))},T.build=function(){var b,c,d=this.$element,e=this.$clone,f=this.options;this.ready&&(this.built&&this.unbuild(),this.$cropper=b=a(k.TEMPLATE),d.addClass(s),e.removeClass(r),this.$container=d.parent().append(b),this.$canvas=b.find(".cropper-canvas").append(e),this.$dragBox=b.find(".cropper-drag-box"),this.$cropBox=c=b.find(".cropper-crop-box"),this.$viewBox=b.find(".cropper-view-box"),this.addListeners(),this.initPreview(),f.aspectRatio=S(f.aspectRatio)||0/0,f.autoCrop?(this.cropped=!0,f.modal&&this.$dragBox.addClass(q)):c.addClass(s),f.background&&b.addClass(x),f.highlight||c.find(".cropper-face").addClass(t),f.guides||c.find(".cropper-dashed").addClass(s),f.movable||c.find(".cropper-face").data("drag","move"),f.resizable||c.find(".cropper-line, .cropper-point").addClass(s),this.setDragMode(f.dragCrop?"crop":"move"),this.built=!0,this.render(),d.one(F,f.built).trigger(F))},T.unbuild=function(){this.built&&(this.built=!1,this.removeListeners(),this.$preview.empty(),this.$preview=null,this.$viewBox=null,this.$cropBox=null,this.$dragBox=null,this.$canvas=null,this.$container=null,this.$cropper.remove(),this.$cropper=null)},a.extend(T,{render:function(){this.initContainer(),this.initCanvas(),this.initCropBox()},initContainer:function(){var a=this.$element,b=this.$container,c=this.$cropper,d=this.options;c.addClass(s),a.removeClass(s),c.css(this.container={width:O(b.width(),S(d.minContainerWidth)||200),height:O(b.height(),S(d.minContainerHeight)||100)}),a.addClass(s),c.removeClass(s)},initCanvas:function(){var b=this.options,c=this.container,d=c.width,e=c.height,f=this.image,g=f.aspectRatio,h={aspectRatio:g,width:d,height:e,left:0,top:0,minLeft:-d,minTop:-e,maxLeft:d,maxTop:e,minWidth:0,minHeight:0,maxWidth:1/0,maxHeight:1/0};e*g>d?b.strict?h.width=e*g:h.height=d/g:b.strict?h.height=d/g:h.width=e*g,h.oldLeft=h.left=(d-h.width)/2,h.oldTop=h.top=(e-h.height)/2,this.canvas=h,this.limitCanvas(),this.initialImage=a.extend({},f),this.initialCanvas=a.extend({},this.canvas),this.renderCanvas()},limitCanvas:function(){var a=this.options,b=this.container,c=b.width,d=b.height,e=this.canvas,f=e.aspectRatio,g=c,h=d;d*f>c?a.strict?g=d*f:h=c/f:a.strict?h=c/f:g=d*f,a.strict?(e.minWidth=g,e.minHeight=h,e.maxLeft=0,e.maxTop=0,e.minLeft=c-g,e.minTop=d-h):(e.minLeft=-g,e.minTop=-h)},renderCanvas:function(b){var c,d,e,f=this.options,g=this.container,j=this.canvas,k=this.image;this.rotated&&(this.rotated=!1,e=i({width:k.width,height:k.height,degree:k.rotate}),c=e.width/e.height,c!==j.aspectRatio&&(j.left-=(e.width-j.width)/2,j.top-=(e.height-j.height)/2,j.width=e.width,j.height=e.height,j.aspectRatio=c,this.limitCanvas())),(j.width>j.maxWidth||j.width<j.minWidth)&&(j.left=j.oldLeft),(j.height>j.maxHeight||j.height<j.minHeight)&&(j.top=j.oldTop),j.width=N(O(j.width,j.minWidth),j.maxWidth),j.height=N(O(j.height,j.minHeight),j.maxHeight),f.strict?(j.minLeft=g.width-j.width,j.minTop=g.height-j.height):(j.minLeft=-j.width,j.minTop=-j.height),j.oldLeft=j.left=N(O(j.left,j.minLeft),j.maxLeft),j.oldTop=j.top=N(O(j.top,j.minTop),j.maxTop),this.$canvas.css({width:j.width,height:j.height,left:j.left,top:j.top}),k.rotate?(d=i({width:j.width,height:j.height,degree:k.rotate,aspectRatio:k.aspectRatio},!0),a.extend(k,{width:d.width,height:d.height,left:(j.width-d.width)/2,top:(j.height-d.height)/2})):a.extend(k,{width:j.width,height:j.height,left:0,top:0}),this.$clone.css({width:k.width,height:k.height,marginLeft:k.left,marginTop:k.top,transform:h(k.rotate)}),b&&(this.preview(),f.crop&&f.crop.call(this.$element,this.getData()))},initCropBox:function(){var b=this.options,c=this.container,d=this.canvas,e=b.strict,f=b.aspectRatio,g=S(b.minCropBoxWidth)||0,h=S(b.minCropBoxHeight)||0,i=S(b.autoCropArea)||.8,j=c.width,k=c.height,l={width:e?j:d.width,height:e?k:d.height,minWidth:g,minHeight:h,maxWidth:j,maxHeight:k};f&&(k*f>j?(l.height=l.width/f,l.maxHeight=j/f):(l.width=l.height*f,l.maxWidth=k*f),e||(l.height*d.aspectRatio>l.width?(l.height=d.height,l.width=l.height*f):(l.width=d.width,l.height=l.width/f)),g?l.minHeight=l.minWidth/f:h&&(l.minWidth=l.minHeight*f)),l.minWidth=N(l.maxWidth,l.minWidth),l.minHeight=N(l.maxHeight,l.minHeight),l.width=O(l.minWidth,l.width*i),l.height=O(l.minHeight,l.height*i),l.oldLeft=l.left=(j-l.width)/2,l.oldTop=l.top=(k-l.height)/2,this.initialCropBox=a.extend({},l),this.cropBox=l,this.cropped&&this.renderCropBox()},renderCropBox:function(){var a=this.options,b=this.container,c=b.width,d=b.height,e=this.$cropBox,f=this.cropBox;(f.width>f.maxWidth||f.width<f.minWidth)&&(f.left=f.oldLeft),(f.height>f.maxHeight||f.height<f.minHeight)&&(f.top=f.oldTop),f.width=N(O(f.width,f.minWidth),f.maxWidth),f.height=N(O(f.height,f.minHeight),f.maxHeight),f.oldLeft=f.left=N(O(f.left,0),c-f.width),f.oldTop=f.top=N(O(f.top,0),d-f.height),a.movable&&e.find(".cropper-face").data("drag",f.width===c&&f.height===d?"move":"all"),e.css({width:f.width,height:f.height,left:f.left,top:f.top}),this.disabled||(this.preview(),a.crop&&a.crop.call(this.$element,this.getData()))}}),T.initPreview=function(){var b=this.url;this.$preview=a(this.options.preview),this.$viewBox.html('<img src="'+b+'">'),this.$preview.each(function(){var c=a(this);c.data({width:c.width(),height:c.height()}).html('<img src="'+b+'" style="display:block;width:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation: 0deg!important">')})},T.preview=function(){var b=this.image,c=this.canvas,d=this.cropBox,e=b.width,f=b.height,g=d.left-c.left-b.left,i=d.top-c.top-b.top,j=b.rotate;this.cropped&&!this.disabled&&(this.$viewBox.find("img").css({width:e,height:f,marginLeft:-g,marginTop:-i,transform:h(j)}),this.$preview.each(function(){var b=a(this),c=b.data(),k=c.width/d.width,l=c.width,m=d.height*k;m>c.height&&(k=c.height/d.height,l=d.width*k,m=c.height),b.width(l).height(m).find("img").css({width:e*k,height:f*k,marginLeft:-g*k,marginTop:-i*k,transform:h(j)})}))},T.addListeners=function(){var b=this.options;this.$element.on(G,b.dragstart).on(H,b.dragmove).on(I,b.dragend).on(J,b.zoomin).on(K,b.zoomout),this.$cropper.on(y,a.proxy(this.dragstart,this)).on(C,a.proxy(this.dblclick,this)),b.zoomable&&b.mouseWheelZoom&&this.$cropper.on(B,a.proxy(this.wheel,this)),m.on(z,this._dragmove=e(this.dragmove,this)).on(A,this._dragend=e(this.dragend,this)),b.responsive&&l.on(D,this._resize=e(this.resize,this))},T.removeListeners=function(){var a=this.options;this.$element.off(G,a.dragstart).off(H,a.dragmove).off(I,a.dragend).off(J,a.zoomin).off(K,a.zoomout),this.$cropper.off(y,this.dragstart).off(C,this.dblclick),a.zoomable&&a.mouseWheelZoom&&this.$cropper.off(B,this.wheel),m.off(z,this._dragmove).off(A,this._dragend),a.responsive&&l.off(D,this._resize)},a.extend(T,{resize:function(){var b,c,d,e=this.$container,f=this.container;this.disabled||(d=e.width()/f.width,(1!==d||e.height()!==f.height)&&(b=this.getCanvasData(),c=this.getCropBoxData(),this.render(),this.setCanvasData(a.each(b,function(a,c){b[a]=c*d})),this.setCropBoxData(a.each(c,function(a,b){c[a]=b*d}))))},dblclick:function(){this.disabled||this.setDragMode(this.$dragBox.hasClass(v)?"move":"crop")},wheel:function(a){var b=a.originalEvent,c=1;this.disabled||(a.preventDefault(),b.deltaY?c=b.deltaY>0?1:-1:b.wheelDelta?c=-b.wheelDelta/120:b.detail&&(c=b.detail>0?1:-1),this.zoom(.1*c))},dragstart:function(b){var c,d,e,f=this.options,g=b.originalEvent,h=g&&g.touches,i=b;if(!this.disabled){if(h){if(e=h.length,e>1){if(!f.zoomable||!f.touchDragZoom||2!==e)return;i=h[1],this.startX2=i.pageX,this.startY2=i.pageY,c="zoom"}i=h[0]}if(c=c||a(i.target).data("drag"),p.test(c)){if(b.preventDefault(),d=a.Event(G,{originalEvent:g,dragType:c}),this.$element.trigger(d),d.isDefaultPrevented())return;this.dragType=c,this.cropping=!1,this.startX=i.pageX,this.startY=i.pageY,"crop"===c&&(this.cropping=!0,this.$dragBox.addClass(q))}}},dragmove:function(b){var c,d,e=this.options,f=b.originalEvent,g=f&&f.touches,h=b,i=this.dragType;if(!this.disabled){if(g){if(d=g.length,d>1){if(!e.zoomable||!e.touchDragZoom||2!==d)return;h=g[1],this.endX2=h.pageX,this.endY2=h.pageY}h=g[0]}if(i){if(b.preventDefault(),c=a.Event(H,{originalEvent:f,dragType:i}),this.$element.trigger(c),c.isDefaultPrevented())return;this.endX=h.pageX,this.endY=h.pageY,this.change()}}},dragend:function(b){var c,d=this.dragType;if(!this.disabled&&d){if(b.preventDefault(),c=a.Event(I,{originalEvent:b.originalEvent,dragType:d}),this.$element.trigger(c),c.isDefaultPrevented())return;this.cropping&&(this.cropping=!1,this.$dragBox.toggleClass(q,this.cropped&&this.options.modal)),this.dragType=""}}}),a.extend(T,{reset:function(){this.disabled||(this.image=a.extend({},this.initialImage),this.canvas=a.extend({},this.initialCanvas),this.renderCanvas(),this.cropped&&(this.cropBox=a.extend({},this.initialCropBox),this.renderCropBox()))},clear:function(){this.cropped&&!this.disabled&&(a.extend(this.cropBox,{left:0,top:0,width:0,height:0}),this.renderCropBox(),this.cropped=!1,this.$dragBox.removeClass(q),this.$cropBox.addClass(s))},destroy:function(){var a=this.$element;this.ready||this.$clone.off("load").remove(),this.unbuild(),a.removeClass(s).removeData("cropper")},replace:function(a){this.ready&&!this.disabled&&a&&this.load(a)},enable:function(){this.built&&(this.disabled=!1,this.$cropper.removeClass(w))},disable:function(){this.built&&(this.disabled=!0,this.$cropper.addClass(w))},move:function(a,c){var d=this.canvas;this.built&&!this.disabled&&b(a)&&b(c)&&(d.left+=a,d.top+=c,this.renderCanvas(!0))},zoom:function(b){var c,d,e,f=this.canvas;if(b=S(b),b&&this.built&&!this.disabled&&this.options.zoomable){if(c=a.Event(b>0?J:K),this.$element.trigger(c),c.isDefaultPrevented())return;b=-1>=b?1/(1-b):1>=b?1+b:b,d=f.width*b,e=f.height*b,f.left-=(d-f.width)/2,f.top-=(e-f.height)/2,f.width=d,f.height=e,this.renderCanvas(!0),this.setDragMode("move")}},rotate:function(a){var b=this.image;a=S(a),a&&this.built&&!this.disabled&&this.options.rotatable&&(b.rotate=(b.rotate+a)%360,this.rotated=!0,this.renderCanvas(!0))},getData:function(){var b,c,d=this.cropBox,e=this.canvas,f=this.image,g=f.rotate;return this.built&&this.cropped?(c={x:d.left-e.left,y:d.top-e.top,width:d.width,height:d.height},b=f.width/f.naturalWidth,a.each(c,function(a,d){d/=b,c[a]=d})):c={x:0,y:0,width:0,height:0},c.rotate=g,c},getContainerData:function(){return this.built?this.container:{}},getImageData:function(){return this.ready?this.image:{}},getCanvasData:function(){var a,b=this.canvas;return this.built&&(a={left:b.left,top:b.top,width:b.width,height:b.height}),a||{}},setCanvasData:function(c){var d=this.canvas,e=d.aspectRatio;this.built&&!this.disabled&&a.isPlainObject(c)&&(b(c.left)&&(d.left=c.left),b(c.top)&&(d.top=c.top),b(c.width)?(d.width=c.width,d.height=c.width/e):b(c.height)&&(d.height=c.height,d.width=c.height*e),this.renderCanvas(!0))},getCropBoxData:function(){var a,b=this.cropBox;return this.built&&this.cropped&&(a={left:b.left,top:b.top,width:b.width,height:b.height}),a||{}},setCropBoxData:function(c){var d=this.cropBox,e=this.options.aspectRatio;this.built&&this.cropped&&!this.disabled&&a.isPlainObject(c)&&(b(c.left)&&(d.left=c.left),b(c.top)&&(d.top=c.top),e?b(c.width)?(d.width=c.width,d.height=d.width/e):b(c.height)&&(d.height=c.height,d.width=d.height*e):(b(c.width)&&(d.width=c.width),b(c.height)&&(d.height=c.height)),this.renderCropBox())},getCroppedCanvas:function(b){var c,d,e,f,g,h,i,k,l,m,n;if(this.built&&this.cropped&&L)return a.isPlainObject(b)||(b={}),n=this.getData(),c=n.width,d=n.height,k=c/d,a.isPlainObject(b)&&(g=b.width,h=b.height,g?(h=g/k,i=g/c):h&&(g=h*k,i=h/d)),e=g||c,f=h||d,l=a("<canvas>")[0],l.width=e,l.height=f,m=l.getContext("2d"),b.fillColor&&(m.fillStyle=b.fillColor,m.fillRect(0,0,e,f)),m.drawImage.apply(m,function(){var a,b,e,f,g,h,k=j(this.$clone[0],this.image),l=k.width,m=k.height,o=[k],p=n.x,q=n.y;return-c>=p||p>l?p=a=e=g=0:0>=p?(e=-p,p=0,a=g=N(l,c+p)):l>=p&&(e=0,a=g=N(c,l-p)),0>=a||-d>=q||q>m?q=b=f=h=0:0>=q?(f=-q,q=0,b=h=N(m,d+q)):m>=q&&(f=0,b=h=N(d,m-q)),o.push(p,q,a,b),i&&(e*=i,f*=i,g*=i,h*=i),g>0&&h>0&&o.push(e,f,g,h),o}.call(this)),l},setAspectRatio:function(a){var b=this.options;this.disabled||c(a)||(b.aspectRatio=S(a)||0/0,this.built&&this.initCropBox())},setDragMode:function(a){var b=this.$dragBox,c=!1,d=!1;if(this.ready&&!this.disabled){switch(a){case"crop":this.options.dragCrop?(c=!0,b.data("drag",a)):d=!0;break;case"move":d=!0,b.data("drag",a);break;default:b.removeData("drag")}b.toggleClass(v,c).toggleClass(u,d)}}}),T.change=function(){var a,b=this.dragType,c=this.canvas,d=this.container,e=d.width,f=d.height,g=this.cropBox,h=g.width,i=g.height,j=g.left,k=g.top,l=j+h,m=k+i,n=!0,o=this.options.aspectRatio,p={x:this.endX-this.startX,y:this.endY-this.startY};switch(o&&(p.X=p.y*o,p.Y=p.x/o),b){case"all":j+=p.x,k+=p.y;break;case"e":if(p.x>=0&&(l>=e||o&&(0>=k||m>=f))){n=!1;break}h+=p.x,o&&(i=h/o,k-=p.Y/2),0>h&&(b="w",h=0);break;case"n":if(p.y<=0&&(0>=k||o&&(0>=j||l>=e))){n=!1;break}i-=p.y,k+=p.y,o&&(h=i*o,j+=p.X/2),0>i&&(b="s",i=0);break;case"w":if(p.x<=0&&(0>=j||o&&(0>=k||m>=f))){n=!1;break}h-=p.x,j+=p.x,o&&(i=h/o,k+=p.Y/2),0>h&&(b="e",h=0);break;case"s":if(p.y>=0&&(m>=f||o&&(0>=j||l>=e))){n=!1;break}i+=p.y,o&&(h=i*o,j-=p.X/2),0>i&&(b="n",i=0);break;case"ne":if(o){if(p.y<=0&&(0>=k||l>=e)){n=!1;break}i-=p.y,k+=p.y,h=i*o}else p.x>=0?e>l?h+=p.x:p.y<=0&&0>=k&&(n=!1):h+=p.x,p.y<=0?k>0&&(i-=p.y,k+=p.y):(i-=p.y,k+=p.y);0>h&&0>i?(b="sw",i=0,h=0):0>h?(b="nw",h=0):0>i&&(b="se",i=0);break;case"nw":if(o){if(p.y<=0&&(0>=k||0>=j)){n=!1;break}i-=p.y,k+=p.y,h=i*o,j+=p.X}else p.x<=0?j>0?(h-=p.x,j+=p.x):p.y<=0&&0>=k&&(n=!1):(h-=p.x,j+=p.x),p.y<=0?k>0&&(i-=p.y,k+=p.y):(i-=p.y,k+=p.y);0>h&&0>i?(b="se",i=0,h=0):0>h?(b="ne",h=0):0>i&&(b="sw",i=0);break;case"sw":if(o){if(p.x<=0&&(0>=j||m>=f)){n=!1;break}h-=p.x,j+=p.x,i=h/o}else p.x<=0?j>0?(h-=p.x,j+=p.x):p.y>=0&&m>=f&&(n=!1):(h-=p.x,j+=p.x),p.y>=0?f>m&&(i+=p.y):i+=p.y;0>h&&0>i?(b="ne",i=0,h=0):0>h?(b="se",h=0):0>i&&(b="nw",i=0);break;case"se":if(o){if(p.x>=0&&(l>=e||m>=f)){n=!1;break}h+=p.x,i=h/o}else p.x>=0?e>l?h+=p.x:p.y>=0&&m>=f&&(n=!1):h+=p.x,p.y>=0?f>m&&(i+=p.y):i+=p.y;0>h&&0>i?(b="nw",i=0,h=0):0>h?(b="sw",h=0):0>i&&(b="ne",i=0);break;case"move":c.left+=p.x,c.top+=p.y,this.renderCanvas(!0),n=!1;break;case"zoom":this.zoom(function(a,b,c,d){var e=M(a*a+b*b),f=M(c*c+d*d);return(f-e)/e}(P(this.startX-this.startX2),P(this.startY-this.startY2),P(this.endX-this.endX2),P(this.endY-this.endY2))),this.startX2=this.endX2,this.startY2=this.endY2,n=!1;break;case"crop":p.x&&p.y&&(a=this.$cropper.offset(),j=this.startX-a.left,k=this.startY-a.top,h=g.minWidth,i=g.minHeight,p.x>0?p.y>0?b="se":(b="ne",k-=i):p.y>0?(b="sw",j-=h):(b="nw",j-=h,k-=i),this.cropped||(this.cropped=!0,this.$cropBox.removeClass(s)))}n&&(g.width=h,g.height=i,g.left=j,g.top=k,this.dragType=b,this.renderCropBox()),this.startX=this.endX,this.startY=this.endY},a.extend(k.prototype,T),k.DEFAULTS={aspectRatio:0/0,autoCropArea:.8,crop:null,preview:"",strict:!0,responsive:!0,checkImageOrigin:!0,modal:!0,guides:!0,highlight:!0,background:!0,autoCrop:!0,dragCrop:!0,movable:!0,resizable:!0,rotatable:!0,zoomable:!0,touchDragZoom:!0,mouseWheelZoom:!0,minCropBoxWidth:0,minCropBoxHeight:0,minContainerWidth:200,minContainerHeight:100,build:null,built:null,dragstart:null,dragmove:null,dragend:null,zoomin:null,zoomout:null},k.setDefaults=function(b){a.extend(k.DEFAULTS,b)},k.TEMPLATE=function(a,b){return b=b.split(","),a.replace(/\d+/g,function(a){return b[a]})}('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-2-9" 3-2="move"></0><0 6="5-crop-9"><1 6="5-view-9"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>',"div,span,drag,data,point,cropper,class,line,dashed,box"),k.other=a.fn.cropper,a.fn.cropper=function(b){var e,f=d(arguments,1);return this.each(function(){var c,d=a(this),g=d.data("cropper");g||d.data("cropper",g=new k(this,b)),"string"==typeof b&&a.isFunction(c=g[b])&&(e=c.apply(g,f))}),c(e)?this:e},a.fn.cropper.Constructor=k,a.fn.cropper.setDefaults=k.setDefaults,a.fn.cropper.noConflict=function(){return a.fn.cropper=k.other,this}});
\ No newline at end of file
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js
new file mode 100644 (file)
index 0000000..d66f658
--- /dev/null
@@ -0,0 +1,7 @@
+/*!
+ * imagesLoaded PACKAGED v3.1.8
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+
+(function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s});
\ No newline at end of file
index 7e714dd..a1e6c76 100644 (file)
@@ -412,7 +412,7 @@ CREATE TABLE sys_file_reference (
        alternative tinytext,
        link varchar(1024) DEFAULT '' NOT NULL,
        downloadname tinytext,
-       crop varchar(64) DEFAULT '' NOT NULL,
+       crop varchar(4000) DEFAULT '' NOT NULL,
 
        PRIMARY KEY (uid),
        KEY parent (pid,deleted),
index 0bcd5a8..abd0510 100644 (file)
                                <source>Link</source>
                        </trans-unit>
                        <trans-unit id="sys_file_reference.crop">
-                               <source>Crop image</source>
+                               <source>Image manipulation</source>
                        </trans-unit>
                        <trans-unit id="sys_file_collection">
                                <source>File collection</source>
index e77917f..02d04ae 100644 (file)
                        <trans-unit id="grid_columnHelp">
                                <source>The column position defines in which area the content is rendered in the frontend.</source>
                        </trans-unit>
+                       <trans-unit id="imwizard.open-editor">
+                               <source>Open Editor</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.supported-types-message">
+                               <source>Image manipulation is only available for supported types:</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.no-image-found">
+                               <source>No image found</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.no-image-found-message">
+                               <source>No image found. Could also be that image is present but that the original dimensions are unknow.</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.image-manipulation">
+                               <source>Image manipulation</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.image-title">
+                               <source>Title</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.original-dimensions">
+                               <source>Original dimensions</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.reset">
+                               <source>Reset</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.accept">
+                               <source>Accept</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.cancel">
+                               <source>Cancel</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.aspect-ratio">
+                               <source>Aspect ratio</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.set-aspect-ratio">
+                               <source>Set Aspect Ratio</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.ratio.16_9">
+                               <source>16:9</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.ratio.4_3">
+                               <source>4:3</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.ratio.1_1">
+                               <source>1:1</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.ratio.free">
+                               <source>Free</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.zoom">
+                               <source>Zoom</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.zoom-in">
+                               <source>Zoom in</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.zoom-out">
+                               <source>Zoom out</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.selection">
+                               <source>Selected Size</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.crop-x">
+                               <source>x:</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.crop-y">
+                               <source>y:</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.crop-width">
+                               <source>width:</source>
+                       </trans-unit>
+                       <trans-unit id="imwizard.crop-height">
+                               <source>height:</source>
+                       </trans-unit>
                </body>
        </file>
 </xliff>
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less
new file mode 100644 (file)
index 0000000..708bb18
--- /dev/null
@@ -0,0 +1,19 @@
+.cropper-line {
+       background-color: #FFFFFF;
+}
+.cropper-point {
+       background-color: #FFFFFF;
+}
+.cropper-point.point-se:before {
+       background-color: #FFFFFF;
+}
+.cropper-view-box {
+       outline: 1px dashed #6699ff;
+       outline-color: rgb(255, 255, 255, 0.75);
+}
+.cropper-bg {
+       background-image: data-uri("../../Images/cropper-background.png");
+}
+.cropper-image-container {
+
+}
\ No newline at end of file
index 59a1ba7..ca81489 100644 (file)
@@ -49,7 +49,6 @@
                color: @modal-warning-color;
                border-bottom-color: @modal-warning-border;
        }
-
        &.t3-modal-danger .modal-header {
                background-color: @modal-danger-bg;
                color: @modal-danger-color;
        .transition(margin-top .1s ease-in);
        border: none;
        overflow: hidden;
+
+       .loadmessage {
+               text-align: center;
+               color: @gray-darker;
+       }
+}
+
+.modal-resize {
+       &.fade .modal-dialog {
+               .transition-property(~"height, width");
+               .transition-duration(.35s);
+               .transition-timing-function(ease);
+       }
+}
+
+.modal-image-manipulation {
+       .modal-body {
+               .col-lg-12 {
+                       padding-right: 450px;
+                       .panel {
+                               margin: 0;
+                               width: 400px;
+                               position: absolute;
+                               top: 0px;
+                               right: 15px;
+                       }
+               }
+       }
+}
+
+.modal.modal-dark {
+
+       color: #FFF;
+       .modal-content {
+               background-color: #212424;
+       }
+       .modal-header {
+               color: #FFF;
+               background-color: #484848;
+               border-bottom-color: #000000;
+       }
+       .modal-body, .modal-footer {
+               background-color: #212424;
+               color: #FFF;
+       }
+       .modal-footer {
+               border-top: none;
+       }
+}
+
+// Modal as panel
+.modal-panel {
+       .modal-panel-body {
+               float: left;
+               width: 400px;
+       }
+       .modal-panel-sidebar-right {
+               width: 300px;
+               float: right;
+               border-left: 1px solid #000000;
+       }
 }
+
+.modal-image-manipulation {
+       .modal-panel-body {
+               padding: 20px;
+               img {
+                       max-width: 100%;
+               }
+       }
+}
\ No newline at end of file
index ef43277..34a827b 100644 (file)
@@ -192,7 +192,20 @@ table {
                }
        }
 }
-
+.table-no-borders {
+       border: none;
+       > thead,
+       > tbody,
+       > tfoot {
+               > tr {
+                       > th,
+                       > td {
+                               border: none;
+                               padding: 2px;
+                       }
+               }
+       }
+}
 //
 // Fits the table in the viewport and makes overflow possible
 //
@@ -259,3 +272,16 @@ table {
                width: auto;
        }
 }
+.table-fit-block {
+       max-width: 100%;
+       width: auto;
+       display: block;
+       margin: 0;
+       > .table {
+               width: auto;
+       }
+}
+.table-spacer-wrap {
+       margin-top: 10px;
+       margin-bottom: 10px;
+}
index a740e7b..42415af 100644 (file)
 // Components from bootstrap plugins
 @import "@{library-directory-prefix}/eonasdan-bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less";
 
+// Image Manipulation Wizard base styles
+@import "@{library-directory-prefix}/cropper/src/less/cropper.less";
+
+
 /*!
  *  Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
  *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
 
 @import "TYPO3/_element_animation.less";
 @import "TYPO3/_element_clipboard.less";
+@import "TYPO3/_element_cropper.less";
 @import "TYPO3/_element_csh.less";
 @import "TYPO3/_element_csm.less";
 @import "TYPO3/_element_docheader.less";
index c9019bb..b9bdf93 100644 (file)
@@ -5609,6 +5609,252 @@ button.close {
   }
 }
 /*!
+ * Cropper v@VERSION
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-@YEAR Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: @DATE
+ */
+.cropper-container {
+  position: relative;
+  overflow: hidden;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-touch-callout: none;
+}
+.cropper-container img {
+  display: block;
+  image-orientation: 0deg !important;
+  width: 100%;
+  height: 100%;
+  min-width: 0 !important;
+  min-height: 0 !important;
+  max-width: none !important;
+  max-height: none !important;
+}
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+.cropper-drag-box {
+  background-color: #fff;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-modal {
+  background-color: #000;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.cropper-view-box {
+  display: block;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  outline: 1px solid #6699ff;
+  outline-color: rgba(102, 153, 255, 0.75);
+}
+.cropper-dashed {
+  position: absolute;
+  display: block;
+  border: 0 dashed #fff;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.cropper-dashed.dashed-h {
+  top: 33.33333333%;
+  left: 0;
+  width: 100%;
+  height: 33.33333333%;
+  border-top-width: 1px;
+  border-bottom-width: 1px;
+}
+.cropper-dashed.dashed-v {
+  top: 0;
+  left: 33.33333333%;
+  width: 33.33333333%;
+  height: 100%;
+  border-right-width: 1px;
+  border-left-width: 1px;
+}
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  position: absolute;
+  display: block;
+  width: 100%;
+  height: 100%;
+  opacity: 0.1;
+  filter: alpha(opacity=10);
+}
+.cropper-face {
+  top: 0;
+  left: 0;
+  cursor: move;
+  background-color: #fff;
+}
+.cropper-line {
+  background-color: #6699ff;
+}
+.cropper-line.line-e {
+  top: 0;
+  right: -3px;
+  width: 5px;
+  cursor: e-resize;
+}
+.cropper-line.line-n {
+  top: -3px;
+  left: 0;
+  height: 5px;
+  cursor: n-resize;
+}
+.cropper-line.line-w {
+  top: 0;
+  left: -3px;
+  width: 5px;
+  cursor: w-resize;
+}
+.cropper-line.line-s {
+  bottom: -3px;
+  left: 0;
+  height: 5px;
+  cursor: s-resize;
+}
+.cropper-point {
+  width: 5px;
+  height: 5px;
+  background-color: #6699ff;
+  opacity: 0.75;
+  filter: alpha(opacity=75);
+}
+.cropper-point.point-e {
+  top: 50%;
+  right: -3px;
+  margin-top: -3px;
+  cursor: e-resize;
+}
+.cropper-point.point-n {
+  top: -3px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: n-resize;
+}
+.cropper-point.point-w {
+  top: 50%;
+  left: -3px;
+  margin-top: -3px;
+  cursor: w-resize;
+}
+.cropper-point.point-s {
+  bottom: -3px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: s-resize;
+}
+.cropper-point.point-ne {
+  top: -3px;
+  right: -3px;
+  cursor: ne-resize;
+}
+.cropper-point.point-nw {
+  top: -3px;
+  left: -3px;
+  cursor: nw-resize;
+}
+.cropper-point.point-sw {
+  bottom: -3px;
+  left: -3px;
+  cursor: sw-resize;
+}
+.cropper-point.point-se {
+  right: -3px;
+  bottom: -3px;
+  width: 20px;
+  height: 20px;
+  cursor: se-resize;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.cropper-point.point-se:before {
+  position: absolute;
+  right: -50%;
+  bottom: -50%;
+  display: block;
+  width: 200%;
+  height: 200%;
+  content: " ";
+  background-color: #6699ff;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+@media (min-width: 768px) {
+  .cropper-point.point-se {
+    width: 15px;
+    height: 15px;
+  }
+}
+@media (min-width: 992px) {
+  .cropper-point.point-se {
+    width: 10px;
+    height: 10px;
+  }
+}
+@media (min-width: 1200px) {
+  .cropper-point.point-se {
+    width: 5px;
+    height: 5px;
+    opacity: 0.75;
+    filter: alpha(opacity=75);
+  }
+}
+.cropper-bg {
+  background-image: url("../img/bg.png");
+}
+.cropper-invisible {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-hide {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: -1;
+  width: auto!important;
+  height: auto!important;
+  min-width: 0!important;
+  min-height: 0!important;
+  max-width: none!important;
+  max-height: none!important;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-hidden {
+  display: none !important;
+}
+.cropper-move {
+  cursor: move;
+}
+.cropper-crop {
+  cursor: crosshair;
+}
+.cropper-disabled .cropper-canvas,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}
+/*!
  *  Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
  *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
  */
@@ -7794,6 +8040,22 @@ table#typo3-clipboard tr.bgColor5 td a {
 table#typo3-clipboard tr.bgColor5 td img {
   vertical-align: middle;
 }
+.cropper-line {
+  background-color: #FFFFFF;
+}
+.cropper-point {
+  background-color: #FFFFFF;
+}
+.cropper-point.point-se:before {
+  background-color: #FFFFFF;
+}
+.cropper-view-box {
+  outline: 1px dashed #6699ff;
+  outline-color: #ffffff;
+}
+.cropper-bg {
+  background-image: url("../../Images/cropper-background.png");
+}
 .t3-help-link {
   display: inline!important;
 }
@@ -8459,6 +8721,62 @@ table#typo3-history-item img {
   border: none;
   overflow: hidden;
 }
+.modal-content .loadmessage {
+  text-align: center;
+  color: #1e1e1e;
+}
+.modal-resize.fade .modal-dialog {
+  -webkit-transition-property: height, width;
+  transition-property: height, width;
+  -webkit-transition-duration: 0.35s;
+  transition-duration: 0.35s;
+  -webkit-transition-timing-function: ease;
+  transition-timing-function: ease;
+}
+.modal-image-manipulation .modal-body .col-lg-12 {
+  padding-right: 450px;
+}
+.modal-image-manipulation .modal-body .col-lg-12 .panel {
+  margin: 0;
+  width: 400px;
+  position: absolute;
+  top: 0px;
+  right: 15px;
+}
+.modal.modal-dark {
+  color: #FFF;
+}
+.modal.modal-dark .modal-content {
+  background-color: #212424;
+}
+.modal.modal-dark .modal-header {
+  color: #FFF;
+  background-color: #484848;
+  border-bottom-color: #000000;
+}
+.modal.modal-dark .modal-body,
+.modal.modal-dark .modal-footer {
+  background-color: #212424;
+  color: #FFF;
+}
+.modal.modal-dark .modal-footer {
+  border-top: none;
+}
+.modal-panel .modal-panel-body {
+  float: left;
+  width: 400px;
+}
+.modal-panel .modal-panel-sidebar-right {
+  width: 300px;
+  float: right;
+  border-left: 1px solid #000000;
+}
+.modal-image-manipulation .modal-panel-body {
+  padding: 20px;
+}
+.modal-image-manipulation .modal-panel-body img {
+  max-width: 100%;
+}
 #typo3-pagetree {
   height: 100%;
 }
@@ -9167,6 +9485,18 @@ fieldset[disabled] .table .btn-default.active {
 .table-vertical-bottom > tfoot > tr > td {
   vertical-align: bottom;
 }
+.table-no-borders {
+  border: none;
+}
+.table-no-borders > thead > tr > th,
+.table-no-borders > tbody > tr > th,
+.table-no-borders > tfoot > tr > th,
+.table-no-borders > thead > tr > td,
+.table-no-borders > tbody > tr > td,
+.table-no-borders > tfoot > tr > td {
+  border: none;
+  padding: 2px;
+}
 .table-fit {
   width: 100%;
   margin-bottom: 1.5em;
@@ -9216,6 +9546,19 @@ fieldset[disabled] .table .btn-default.active {
 .table-fit-inline-block > .table {
   width: auto;
 }
+.table-fit-block {
+  max-width: 100%;
+  width: auto;
+  display: block;
+  margin: 0;
+}
+.table-fit-block > .table {
+  width: auto;
+}
+.table-spacer-wrap {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
 .tooltip-inner {
   padding: 5px 10px;
 }
diff --git a/typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png b/typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png
new file mode 100644 (file)
index 0000000..8eddf72
Binary files /dev/null and b/typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png differ