[FEATURE] Introduce new Icon API 59/41759/23
authorFrank Nägler <typo3@naegler.net>
Mon, 20 Jul 2015 21:34:19 +0000 (23:34 +0200)
committerBenni Mack <benni@typo3.org>
Thu, 6 Aug 2015 15:42:26 +0000 (17:42 +0200)
The logic for working with icons, icon sizes and icon overlays is now bundled into the new IconFactory class.
The new IconFactory will replace the old icon skinning API step by step.

All core icons will be registered directly in the IconRegistry class, third
party extensions must use IconRegistry::registerIcon() to overwrite existing
icons or add additional icons to the IconFactory.

The IconFactory takes care of the correct icon and overlay sizes and the markup.

Resolves: #68741
Releases: master
Change-Id: I731e077290b58298c2c603eeb8961e8a3d4c62d3
Reviewed-on: http://review.typo3.org/41759
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
22 files changed:
Build/Resources/Public/Less/Component/icon.less [new file with mode: 0644]
Build/Resources/Public/Less/_minimal.less
typo3/sysext/core/Classes/Imaging/Dimension.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/Icon.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconFactory.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconProvider/BitmapIconProvider.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconProvider/FontawesomeIconProvider.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconProvider/SvgIconProvider.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconProviderInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Imaging/IconRegistry.php [new file with mode: 0644]
typo3/sysext/core/Classes/Utility/PathUtility.php
typo3/sysext/core/Classes/ViewHelpers/IconViewHelper.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-68741-IntroduceNewIconFactoryAsBaseForReplaceTheIconSkinningAPI.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/DimensionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconFactoryTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/BitmapIconProviderTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/FontawesomeIconProviderTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/SvgIconProviderTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconRegistryTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php
typo3/sysext/t3skin/Resources/Public/Css/backend.css

diff --git a/Build/Resources/Public/Less/Component/icon.less b/Build/Resources/Public/Less/Component/icon.less
new file mode 100644 (file)
index 0000000..e0d9a05
--- /dev/null
@@ -0,0 +1,90 @@
+//
+// Icon
+// ====
+// General CSS for Icons
+//
+//
+// Example Usage
+// -------------
+//
+// <span class="icon icon-size-large icon-provider-svg">
+//   <span class="icon-markup">
+//     <img src="#" width="16" height="16">
+//   </span>
+//   <span class="icon-overlay">
+//     <img src="#" width="8" height="8">
+//   </span>
+// </span>
+//
+
+
+//
+// Variables
+//
+@icon-size-small:        16px;
+@icon-size-default:      32px;
+@icon-size-large:        48px;
+@icon-unify-modifier:    0.86;
+
+
+//
+// Component
+//
+.icon {
+       position: relative;
+       display: inline-block;
+       overflow: hidden;
+       img {
+               display: block;
+               height: 100%;
+               width: 100%;
+       }
+}
+.icon-markup {
+       position: absolute;
+       height: 100%;
+       width: 100%;
+       text-align: center;
+}
+.icon-overlay {
+       position: absolute;
+       bottom: 0;
+       right: 0;
+       height: 50%;
+       width: 50%;
+       text-align: center;
+}
+
+//
+// Variants
+//
+.icon-size(@identifier; @size) {
+       .icon-size-@{identifier} {
+               height: @size;
+               width: @size;
+               .icon-unify {
+                       line-height: @size;
+                       font-size: ceil(@size * @icon-unify-modifier);
+               }
+               .icon-overlay {
+                       .icon-unify {
+                               line-height: ceil(@size / 2);
+                               font-size: ceil(ceil(@size / 2) * @icon-unify-modifier);
+                       }
+               }
+       }
+}
+.icon-size(small; @icon-size-small);
+.icon-size(default; @icon-size-default);
+.icon-size(large; @icon-size-large);
+
+//
+// Special icons
+//
+.icon-default-not-found {
+       color: @brand-danger;
+}
+
+.icon-overlay-read-only {
+       color: @brand-danger;
+}
index 9c40319..310e5ec 100644 (file)
@@ -72,6 +72,7 @@
 @import "Component/autocomplete.less";
 @import "Component/avatar.less";
 @import "Component/callout.less";
+@import "Component/icon.less";
 
 //
 // Bootstrap Utility classes
diff --git a/typo3/sysext/core/Classes/Imaging/Dimension.php b/typo3/sysext/core/Classes/Imaging/Dimension.php
new file mode 100644 (file)
index 0000000..e10404e
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging;
+
+/*
+ * 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!
+ */
+
+/**
+ * Dimension class holds width and height for an icon
+ */
+class Dimension {
+
+       /**
+        * @var int
+        */
+       protected $width;
+
+       /**
+        * @var int
+        */
+       protected $height;
+
+       /**
+        * Constructor which fetches the size and resolves it to a pixel size
+        *
+        * @param string $size the icon size
+        *
+        * @throws \InvalidArgumentException
+        */
+       public function __construct($size = Icon::SIZE_DEFAULT) {
+               switch ($size) {
+                       case Icon::SIZE_LARGE:
+                               $sizeInPixel = 48;
+                               break;
+                       case Icon::SIZE_DEFAULT:
+                               $sizeInPixel = 32;
+                               break;
+                       case Icon::SIZE_SMALL:
+                       case Icon::SIZE_OVERLAY:
+                               $sizeInPixel = 16;
+                               break;
+                       default:
+                               throw new \InvalidArgumentException('The given size ' . $size . ' is not a valid size, see Icon class for options', 1438871603);
+               }
+
+               $this->width = (int)$sizeInPixel;
+               $this->height = (int)$sizeInPixel;
+       }
+
+       /**
+        * Returns the width
+        * @return int
+        */
+       public function getWidth() {
+               return $this->width;
+       }
+
+       /**
+        * Returns the height
+        * @return int
+        */
+       public function getHeight() {
+               return $this->height;
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/Icon.php b/typo3/sysext/core/Classes/Imaging/Icon.php
new file mode 100644 (file)
index 0000000..28650eb
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging;
+
+/*
+ * 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\Utility\GeneralUtility;
+
+/**
+ * Icon object, holds all information for one icon, identified by the "identifier" property.
+ * Is available to render itself as string.
+ */
+class Icon {
+
+       /**
+        * @var string the small size
+        */
+       const SIZE_SMALL = 'small'; // 16
+
+       /**
+        * @var string the default size
+        */
+       const SIZE_DEFAULT = 'default'; // 32
+
+       /**
+        * @var string the large size
+        */
+       const SIZE_LARGE = 'large'; // 48
+
+       /**
+        * @internal
+        * @var string the overlay size, which depends on icon size
+        */
+       const SIZE_OVERLAY = 'overlay';
+
+       /**
+        * The identifier which the PHP code that calls the IconFactory hands over
+        * @var string
+        */
+       protected $identifier;
+
+       /**
+        * The identifier for a possible overlay icon
+        * @var Icon
+        */
+       protected $overlayIcon = NULL;
+
+       /**
+        * Contains the size string ("large", "small" or "default")
+        * @var string
+        */
+       protected $size = '';
+
+       /**
+        * @var Dimension
+        */
+       protected $dimension;
+
+       /**
+        * @var string
+        */
+       protected $markup;
+
+       /**
+        * @internal this method is used for internal processing, to get the prepared and final markup use render()
+        * @return string
+        */
+       public function getMarkup() {
+               return $this->markup;
+       }
+
+       /**
+        * @param string $markup
+        */
+       public function setMarkup($markup) {
+               $this->markup = $markup;
+       }
+
+       /**
+        * @return string
+        */
+       public function getIdentifier() {
+               return $this->identifier;
+       }
+
+       /**
+        * @param string $identifier
+        */
+       public function setIdentifier($identifier) {
+               $this->identifier = $identifier;
+       }
+
+       /**
+        * @return Icon
+        */
+       public function getOverlayIcon() {
+               return $this->overlayIcon;
+       }
+
+       /**
+        * @param Icon $overlayIcon
+        */
+       public function setOverlayIcon($overlayIcon) {
+               $this->overlayIcon = $overlayIcon;
+       }
+
+       /**
+        * @return string
+        */
+       public function getSize() {
+               return $this->size;
+       }
+
+       /**
+        * Sets the size and creates the new dimension
+        * @param string $size
+        */
+       public function setSize($size) {
+               $this->size = $size;
+               $this->dimension = GeneralUtility::makeInstance(Dimension::class, $size);
+       }
+
+       /**
+        * @return Dimension
+        */
+       public function getDimension() {
+               return $this->dimension;
+       }
+
+       /**
+        * Render the icon as HTML code
+        *
+        * @return string
+        */
+       public function render() {
+               return $this->__toString();
+       }
+
+       /**
+        * Render the icon as HTML code
+        *
+        * @return string
+        */
+       public function __toString() {
+               $overlayIconMarkup = '';
+               if ($this->overlayIcon !== NULL) {
+                       $overlayIconMarkup = '<span class="icon-overlay icon-' . htmlspecialchars($this->overlayIcon->getIdentifier()) . '">' . $this->overlayIcon->getMarkup() . '</span>';
+               }
+               return str_replace('{overlayMarkup}', $overlayIconMarkup, $this->wrappedIcon());
+       }
+
+       /**
+        * Wrap icon markup in unified HTML code
+        *
+        * @return string
+        */
+       protected function wrappedIcon() {
+               $markup = array();
+               $markup[] = '<span class="icon icon-size-' . $this->size . ' icon-' . htmlspecialchars($this->getIdentifier()) . '">';
+               $markup[] = '   <span class="icon-markup">';
+               $markup[] = $this->getMarkup();
+               $markup[] = '   </span>';
+               $markup[] = '   {overlayMarkup}';
+               $markup[] = '</span>';
+
+               return implode(LF, $markup);
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconFactory.php b/typo3/sysext/core/Classes/Imaging/IconFactory.php
new file mode 100644 (file)
index 0000000..a596932
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging;
+
+/*
+ * 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\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * The main factory class, which acts as the entrypoint for generating an Icon object which
+ * is responsible for rendering an icon. Checks for the correct icon provider through the IconRegistry.
+ */
+class IconFactory {
+
+       /**
+        * @var IconRegistry
+        */
+       protected $iconRegistry;
+
+       /**
+        * @param IconRegistry $iconRegistry
+        */
+       public function __construct(IconRegistry $iconRegistry = NULL) {
+               $this->iconRegistry = $iconRegistry ? $iconRegistry : GeneralUtility::makeInstance(IconRegistry::class);
+       }
+
+       /**
+        * @param string $identifier
+        * @param string $size
+        * @param string $overlayIdentifier
+        *
+        * @return Icon
+        */
+       public function getIcon($identifier, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = NULL) {
+               if (!$this->iconRegistry->isRegistered($identifier)) {
+                       $identifier = $this->iconRegistry->getDefaultIconIdentifier();
+               }
+
+               $icon = $this->createIcon($identifier, $size, $overlayIdentifier);
+               $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
+               /** @var IconProviderInterface $iconProvider */
+               $iconProvider = GeneralUtility::makeInstance($iconConfiguration['provider']);
+               $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
+               return $icon;
+       }
+
+       /**
+        * Creates an icon object
+        *
+        * @param string $identifier
+        * @param string $size "large" "small" or "default", see the constants of the Icon class
+        * @param string $overlayIdentifier
+        * @return Icon
+        */
+       protected function createIcon($identifier, $size, $overlayIdentifier = NULL) {
+               $icon = GeneralUtility::makeInstance(Icon::class);
+               $icon->setIdentifier($identifier);
+               $icon->setSize($size);
+               if ($overlayIdentifier !== NULL) {
+                       $icon->setOverlayIcon($this->getIcon($overlayIdentifier, Icon::SIZE_OVERLAY));
+               }
+               return $icon;
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconProvider/BitmapIconProvider.php b/typo3/sysext/core/Classes/Imaging/IconProvider/BitmapIconProvider.php
new file mode 100644 (file)
index 0000000..0684137
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconProviderInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+/**
+ * Class BitmapIconProvider which provides icons that are classic <img> tags
+ */
+class BitmapIconProvider implements IconProviderInterface {
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        */
+       public function prepareIconMarkup(Icon $icon, array $options = array()) {
+               $icon->setMarkup($this->generateMarkup($icon, $options));
+       }
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        *
+        * @return string
+        */
+       protected function generateMarkup(Icon $icon, array $options) {
+               $source = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($options['source']));
+               return '<img src="' . htmlspecialchars($source) . '" width="' . $icon->getDimension()->getWidth() . '" height="' . $icon->getDimension()->getHeight() . '" />';
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconProvider/FontawesomeIconProvider.php b/typo3/sysext/core/Classes/Imaging/IconProvider/FontawesomeIconProvider.php
new file mode 100644 (file)
index 0000000..e535f74
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconProviderInterface;
+
+/**
+ * Class FontawesomeIconProvider
+ */
+class FontawesomeIconProvider implements IconProviderInterface {
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        */
+       public function prepareIconMarkup(Icon $icon, array $options = array()) {
+               $icon->setMarkup($this->generateMarkup($icon, $options));
+       }
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        *
+        * @return string
+        */
+       protected function generateMarkup(Icon $icon, array $options) {
+               $additionalClasses = (!empty($options['additionalClasses'])) ? ' ' . $options['additionalClasses'] : '';
+               return '<span class="icon-unify"><i class="fa fa-' . htmlspecialchars($options['name']) . htmlspecialchars($additionalClasses) . '"></i></span>';
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconProvider/SvgIconProvider.php b/typo3/sysext/core/Classes/Imaging/IconProvider/SvgIconProvider.php
new file mode 100644 (file)
index 0000000..ce27745
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconProviderInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+/**
+ * Class SvgIconProvider
+ */
+class SvgIconProvider implements IconProviderInterface {
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        */
+       public function prepareIconMarkup(Icon $icon, array $options = array()) {
+               $icon->setMarkup($this->generateMarkup($icon, $options));
+       }
+
+       /**
+        * @param Icon $icon
+        * @param array $options
+        *
+        * @return string
+        */
+       protected function generateMarkup(Icon $icon, array $options) {
+               $source = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($options['source']));
+               return '<img src="' . $source . '" width="' . $icon->getDimension()->getWidth() . '" height="' . $icon->getDimension()->getHeight() . '" />';
+       }
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconProviderInterface.php b/typo3/sysext/core/Classes/Imaging/IconProviderInterface.php
new file mode 100644 (file)
index 0000000..48c276b
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging;
+
+/*
+ * 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!
+ */
+
+/**
+ * Interface IconProviderInterface
+ */
+interface IconProviderInterface {
+
+       /**
+        * Prepare the icon markup and set it to the icon by setMarkup()
+        *
+        * @param Icon $icon
+        * @param array $options
+        */
+       public function prepareIconMarkup(Icon $icon, array $options = array());
+}
diff --git a/typo3/sysext/core/Classes/Imaging/IconRegistry.php b/typo3/sysext/core/Classes/Imaging/IconRegistry.php
new file mode 100644 (file)
index 0000000..14a29a8
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+namespace TYPO3\CMS\Core\Imaging;
+
+/*
+ * 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\Exception;
+use TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider;
+
+/**
+ * Class IconRegistry, which makes it possible to register custom icons
+ * from within an extension.
+ */
+class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * Registered icons
+        *
+        * @var array
+        */
+       protected $icons = array(
+               // @TODO: replace default icon before merge!
+               // default icon, fallback
+               'default-not-found' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'times-circle',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+
+               // Action icons
+               'actions-document-close' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'times',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+               'actions-document-export-csv' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'download',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+               'actions-document-export-t3d' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'download',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+               'actions-document-import-t3d' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'upload',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+               'actions-document-open' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'pencil',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ),
+
+               // OVERLAYS
+               'overlay-read-only' => array(
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'minus-circle',
+                               'additionalClasses' => ''
+                       )
+               ),
+       );
+
+       /**
+        * @var string
+        */
+       protected $defaultIconIdentifier = 'default-not-found';
+
+       /**
+        * @param $identifier
+        *
+        * @return bool
+        */
+       public function isRegistered($identifier) {
+               return !empty($this->icons[$identifier]);
+       }
+
+       /**
+        * @return string
+        */
+       public function getDefaultIconIdentifier() {
+               return $this->defaultIconIdentifier;
+       }
+
+       /**
+        * Registers an icon to be available inside the Icon Factory
+        *
+        * @param string $identifier
+        * @param string $iconProviderClassName
+        * @param array $options
+        *
+        * @throws \InvalidArgumentException
+        */
+       public function registerIcon($identifier, $iconProviderClassName, array $options = array()) {
+               if (!in_array(IconProviderInterface::class, class_implements($iconProviderClassName))) {
+                       throw new \InvalidArgumentException('An IconProvider must implement ' . IconProviderInterface::class, 1437425803);
+               }
+               $this->icons[$identifier] = array(
+                       'provider' => $iconProviderClassName,
+                       'options' => $options
+               );
+       }
+
+       /**
+        * Fetches the configuration provided by registerIcon()
+        *
+        * @param string $identifier the icon identifier
+        * @return mixed
+        * @throws Exception
+        */
+       public function getIconConfigurationByIdentifier($identifier) {
+               if (!$this->isRegistered($identifier)) {
+                       throw new Exception('Icon with identifier "' . $identifier . '" is not registered"', 1437425804);
+               }
+               return $this->icons[$identifier];
+       }
+}
index fef7793..c444978 100644 (file)
@@ -39,7 +39,10 @@ class PathUtility {
        static public function getAbsoluteWebPath($targetPath) {
                if (self::isAbsolutePath($targetPath)) {
                        if (StringUtility::beginsWith($targetPath, PATH_site)) {
-                               $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . self::stripPathSitePrefix($targetPath);
+                               $targetPath = self::stripPathSitePrefix($targetPath);
+                               if (!defined('TYPO3_cliMode')) {
+                                       $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
+                               }
                        }
                } elseif (strpos($targetPath, '://') !== FALSE) {
                        return $targetPath;
@@ -47,7 +50,9 @@ class PathUtility {
                        // Make an absolute path out of it
                        $targetPath = GeneralUtility::resolveBackPath(dirname(PATH_thisScript) . '/' . $targetPath);
                        $targetPath = self::stripPathSitePrefix($targetPath);
-                       $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
+                       if (!defined('TYPO3_cliMode')) {
+                               $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
+                       }
                }
                return $targetPath;
        }
diff --git a/typo3/sysext/core/Classes/ViewHelpers/IconViewHelper.php b/typo3/sysext/core/Classes/ViewHelpers/IconViewHelper.php
new file mode 100644 (file)
index 0000000..c956733
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+namespace TYPO3\CMS\Core\ViewHelpers;
+
+/*
+ * 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\Imaging\IconFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface;
+use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface;
+
+/**
+ * Displays sprite icon identified by iconName key
+ * @internal
+ */
+class IconViewHelper extends AbstractViewHelper implements CompilableInterface {
+
+       /**
+        * Prints icon html for $identifier key
+        *
+        * @param string $identifier
+        * @param string $size
+        * @param string $overlay
+        * @return string
+        */
+       public function render($identifier, $size = IconFactory::SIZE_SMALL, $overlay = NULL) {
+               return static::renderStatic(
+                       array(
+                               'identifier' => $identifier,
+                               'size' => $size,
+                               'overlay' => $overlay
+                       ),
+                       $this->buildRenderChildrenClosure(),
+                       $this->renderingContext
+               );
+       }
+
+       /**
+        * Print icon html for $identifier key
+        *
+        * @param array $arguments
+        * @param \Closure $renderChildrenClosure
+        * @param RenderingContextInterface $renderingContext
+        * @return string
+        */
+       static public function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) {
+               $identifier = $arguments['identifier'];
+               $size = $arguments['size'];
+               $overlay = $arguments['overlay'];
+               /** @var IconFactory $iconApi */
+               $iconApi = GeneralUtility::makeInstance(IconFactory::class);
+               return $iconApi->getIcon($identifier, $size, $overlay)->render();
+       }
+
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-68741-IntroduceNewIconFactoryAsBaseForReplaceTheIconSkinningAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-68741-IntroduceNewIconFactoryAsBaseForReplaceTheIconSkinningAPI.rst
new file mode 100644 (file)
index 0000000..fe37456
--- /dev/null
@@ -0,0 +1,84 @@
+====================================================================================
+Feature: #68741 - Introduce new IconFactory as base to replace the icon skinning API
+====================================================================================
+
+Description
+===========
+
+The logic for working with icons, icon sizes and icon overlays is now bundled into the new ``IconFactory`` class.
+The new icon factory will replace the old icon skinning API step by step.
+
+All core icons will be registered directly in the ``IconRegistry`` class, third party extensions must use
+``IconRegistry::registerIcon()`` to overwrite existing icons or add additional icons to the icon factory.
+
+The ``IconFactory`` takes care of the correct icon and overlay size and the markup.
+
+
+IconProvider
+------------
+
+The core implement three icon provider classes, which all implements the ``IconProviderInterface``.
+
+* ``BitmapIconProvider`` for all kind of bitmap icons for gif, png and jpg files
+* ``FontawesomeIconProvider`` for font icons from fontawesome.io
+* ``SvgIconProvider`` for svg icons
+
+Third party extensions can provide own icon provider classes, each class must implement the ``IconProviderInterface``.
+
+
+Register an icon
+----------------
+
+.. code-block:: php
+
+       /*
+        * Put the following code into your ext_localconf.php file of your extension.
+        *
+        * @param string $identifier the icon identifier
+        * @param string $iconProviderClassName the icon provider class name
+        * @param array $options provider specific options, please reference the icon provider class
+        */
+       IconRegistry::registerIcon($identifier, $iconProviderClassName, array $options = array());
+
+
+Use an icon
+-----------
+
+To use an icon, you need at least the icon identifier. The default size is small which currently means an icon with 16x16px.
+The third parameter can be used to add an additional icon as overlay, which can be any registered icon.
+
+The ``IconFactory`` provides only the following constants for Icon sizes:
+
+* ``IconFactory::SIZE_SMALL`` which currently means 16x16 px
+* ``IconFactory::SIZE_DEFAULT`` which currently means 32x32 px
+* ``IconFactory::SIZE_LARGE`` which currently means 48x48 px
+
+All the sizes can change in future, so please make use of the constants for an unified layout.
+
+.. code-block:: php
+
+       $iconApi = GeneralUtility::makeInstance(IconFactory::class);
+       $iconApi->getIcon($identifier, IconFactory::SIZE_SMALL, $overlay)->render();
+
+
+ViewHelper
+----------
+
+The core provides a fluid ViewHelper which makes it really easy to use icons within a fluid view.
+
+.. code-block:: html
+
+       {namespace core = TYPO3\CMS\Core\ViewHelpers}
+       <core:icon identifier="my-icon-identifier" size="small" />
+       <!-- use the "default" size if none given ->
+       <core:icon identifier="my-icon-identifier" />
+       <core:icon identifier="my-icon-identifier" size="large" />
+       <core:icon identifier="my-icon-identifier" size="small" overlay="overlay-identifier" />
+       <core:icon identifier="my-icon-identifier" size="default" overlay="overlay-identifier" />
+       <core:icon identifier="my-icon-identifier" size="large" overlay="overlay-identifier" />
+
+
+Impact
+======
+
+No impact
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/DimensionTest.php b/typo3/sysext/core/Tests/Unit/Imaging/DimensionTest.php
new file mode 100644 (file)
index 0000000..6931be1
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\Dimension
+ */
+class DimensionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\Dimension
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var int
+        */
+       protected $width = 32;
+
+       /**
+        * @var int
+        */
+       protected $height = 32;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->subject = new \TYPO3\CMS\Core\Imaging\Dimension(Icon::SIZE_DEFAULT);
+       }
+
+       /**
+        * @test
+        */
+       public function getWidthReturnsValidInteger() {
+               $value = $this->subject->getWidth();
+               $this->assertEquals($this->width, $value);
+               $this->assertInternalType('int', $value);
+       }
+
+       /**
+        * @test
+        */
+       public function getHeightReturnsValidInteger() {
+               $value = $this->subject->getHeight();
+               $this->assertEquals($this->height, $value);
+               $this->assertInternalType('int', $value);
+       }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconFactoryTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconFactoryTest.php
new file mode 100644 (file)
index 0000000..2b46e12
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging;
+
+/*
+ * 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 Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\IconFactory
+ */
+class IconFactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconFactory
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var string
+        */
+       protected $notRegisteredIconIdentifier = 'my-super-unregistered-identifier';
+
+       /**
+        * @var string
+        */
+       protected $registeredIconIdentifier = 'actions-document-close';
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconRegistry
+        */
+       protected $iconRegistryMock;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->iconRegistryMock = $this->prophesize(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
+               $this->subject = new IconFactory($this->iconRegistryMock->reveal());
+
+               $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(TRUE);
+               $this->iconRegistryMock->getIconConfigurationByIdentifier(Argument::any())->willReturn([
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'times',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ]);
+       }
+
+       /**
+        * DataProvider for icon sizes
+        *
+        * @return array
+        */
+       public function differentSizesDataProvider() {
+               return [
+                       ['size ' . Icon::SIZE_SMALL => ['input' => Icon::SIZE_SMALL, 'expected' => Icon::SIZE_SMALL]],
+                       ['size ' . Icon::SIZE_DEFAULT => ['input' => Icon::SIZE_DEFAULT, 'expected' => Icon::SIZE_DEFAULT]],
+                       ['size ' . Icon::SIZE_LARGE => ['input' => Icon::SIZE_LARGE, 'expected' => Icon::SIZE_LARGE]]
+               ];
+       }
+
+       /**
+        * @test
+        */
+       public function getIconReturnsIconWithCorrectMarkupWrapperIfRegisteredIconIdentifierIsUsed() {
+               $this->assertContains('<span class="icon-markup">',
+                       $this->subject->getIcon($this->registeredIconIdentifier)->render());
+       }
+
+       /**
+        * @test
+        */
+       public function getIconByIdentifierReturnsIconWithCorrectMarkupIfRegisteredIconIdentifierIsUsed() {
+               $this->assertContains('<span class="icon icon-size-default icon-actions-document-close">',
+                       $this->subject->getIcon($this->registeredIconIdentifier)->render());
+       }
+
+       /**
+        * @test
+        * @dataProvider differentSizesDataProvider
+        */
+       public function getIconByIdentifierAndSizeReturnsIconWithCorrectMarkupIfRegisteredIconIdentifierIsUsed($size) {
+               $this->assertContains('<span class="icon icon-size-' . $size['expected'] . ' icon-actions-document-close">',
+                       $this->subject->getIcon($this->registeredIconIdentifier, $size['input'])->render());
+       }
+
+       /**
+        * @test
+        * @dataProvider differentSizesDataProvider
+        */
+       public function getIconByIdentifierAndSizeAndWithOverlayReturnsIconWithCorrectOverlayMarkupIfRegisteredIconIdentifierIsUsed($size) {
+               $this->assertContains('<span class="icon-overlay icon-overlay-read-only">',
+                       $this->subject->getIcon($this->registeredIconIdentifier, $size['input'], 'overlay-read-only')->render());
+       }
+
+       /**
+        * @test
+        */
+       public function getIconReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed() {
+               $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(FALSE);
+               $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
+               $this->iconRegistryMock->getIconConfigurationByIdentifier('default-not-found')->willReturn([
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'times-circle',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ]);
+               $this->assertContains('<span class="icon icon-size-default icon-default-not-found">',
+                       $this->subject->getIcon($this->notRegisteredIconIdentifier)->render());
+       }
+
+       /**
+        * @test
+        * @dataProvider differentSizesDataProvider
+        */
+       public function getIconByIdentifierAndSizeReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed($size) {
+               $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(FALSE);
+               $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
+               $this->iconRegistryMock->getIconConfigurationByIdentifier('default-not-found')->willReturn([
+                       'provider' => FontawesomeIconProvider::class,
+                       'options' => array(
+                               'name' => 'times-circle',
+                               'additionalClasses' => 'fa-fw'
+                       )
+               ]);
+               $this->assertContains('<span class="icon icon-size-' . $size['expected'] . ' icon-default-not-found">',
+                       $this->subject->getIcon($this->notRegisteredIconIdentifier, $size['input'])->render());
+       }
+
+       /**
+        * @test
+        * @dataProvider differentSizesDataProvider
+        */
+       public function getIconByIdentifierAndSizeAndOverlayReturnsNotFoundIconWithCorrectMarkupIfUnregisteredIdentifierIsUsed($size) {
+               $this->assertContains('<span class="icon-overlay icon-overlay-read-only">',
+                       $this->subject->getIcon($this->notRegisteredIconIdentifier, $size['input'], 'overlay-read-only')->render());
+       }
+
+       /**
+        * @test
+        */
+       public function getIconThrowsExceptionIfInvalidSizeIsGiven() {
+               $this->setExpectedException('InvalidArgumentException');
+               $this->subject->getIcon($this->registeredIconIdentifier, 'foo')->render();
+       }
+
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/BitmapIconProviderTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/BitmapIconProviderTest.php
new file mode 100644 (file)
index 0000000..9b217b3
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider
+ */
+class BitmapIconProviderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\Icon
+        */
+       protected $icon = NULL;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->subject = new \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider();
+               $this->icon = GeneralUtility::makeInstance(Icon::class);
+               $this->icon->setIdentifier('foo');
+               $this->icon->setSize(Icon::SIZE_SMALL);
+       }
+
+       /**
+        * @test
+        */
+       public function prepareIconMarkupWithRelativeSourceReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('source' => 'fileadmin/foo.png'));
+               $this->assertEquals('<img src="fileadmin/foo.png" width="16" height="16" />', $this->icon->getMarkup());
+       }
+
+       /**
+        * @test
+        */
+       public function prepareIconMarkupEXTSourceReferenceReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('source' => 'EXT:core/Resources/Public/Images/foo.png'));
+               $this->assertEquals('<img src="typo3/sysext/core/Resources/Public/Images/foo.png" width="16" height="16" />', $this->icon->getMarkup());
+       }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/FontawesomeIconProviderTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/FontawesomeIconProviderTest.php
new file mode 100644 (file)
index 0000000..6825c44
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider
+ */
+class FontawesomeIconProviderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var Icon
+        */
+       protected $icon = NULL;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->subject = new \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider();
+               $this->icon = GeneralUtility::makeInstance(Icon::class);
+               $this->icon->setIdentifier('foo');
+               $this->icon->setSize(Icon::SIZE_SMALL);
+       }
+
+       /**
+        * @test
+        */
+       public function prepareIconMarkupWithNameReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('name' => 'times'));
+               $this->assertEquals('<span class="icon-unify"><i class="fa fa-times"></i></span>', $this->icon->getMarkup());
+       }
+
+       /**
+        * @test
+        */
+       public function prepareIconMarkupWithNameAndAdditionalClassesReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('name' => 'times', 'additionalClasses' => 'foo'));
+               $this->assertEquals('<span class="icon-unify"><i class="fa fa-times foo"></i></span>', $this->icon->getMarkup());
+       }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/SvgIconProviderTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconProvider/SvgIconProviderTest.php
new file mode 100644 (file)
index 0000000..013ddb6
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging\IconProvider;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider
+ */
+class SvgIconProviderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var Icon
+        */
+       protected $icon = NULL;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->subject = new \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider();
+               $this->icon = GeneralUtility::makeInstance(Icon::class);
+               $this->icon->setIdentifier('foo');
+               $this->icon->setSize(Icon::SIZE_SMALL);
+       }
+
+       /**
+        * @test
+        */
+       public function prepareIconMarkupWithRelativeSourceReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('source' => 'fileadmin/foo.svg'));
+               $this->assertEquals('<img src="fileadmin/foo.svg" width="16" height="16" />', $this->icon->getMarkup());
+       }
+
+       /**
+        * @test
+        */
+       public function getIconWithEXTSourceReferenceReturnsInstanceOfIconWithCorrectMarkup() {
+               $this->subject->prepareIconMarkup($this->icon, array('source' => 'EXT:core/Resources/Public/Images/foo.svg'));
+               $this->assertEquals('<img src="typo3/sysext/core/Resources/Public/Images/foo.svg" width="16" height="16" />', $this->icon->getMarkup());
+       }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconRegistryTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconRegistryTest.php
new file mode 100644 (file)
index 0000000..d719ebb
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging;
+
+/*
+ * 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\Imaging\IconProvider\FontawesomeIconProvider;
+use TYPO3\CMS\Core\Imaging\IconProviderInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\IconRegistry
+ */
+class IconRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\IconRegistry
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var string
+        */
+       protected $notRegisteredIconIdentifier = 'my-super-unregistered-identifier';
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->subject = new \TYPO3\CMS\Core\Imaging\IconRegistry();
+       }
+
+       /**
+        * @test
+        */
+       public function getDefaultIconIdentifierReturnsTheCorrectDefaultIconIdentifierString() {
+               $result = $this->subject->getDefaultIconIdentifier();
+               $this->assertEquals($result, 'default-not-found');
+       }
+
+       /**
+        * @test
+        */
+       public function isRegisteredReturnsTrueForRegisteredIcon() {
+               $result = $this->subject->isRegistered($this->subject->getDefaultIconIdentifier());
+               $this->assertEquals($result, TRUE);
+       }
+
+       /**
+        * @test
+        */
+       public function isRegisteredReturnsFalseForNotRegisteredIcon() {
+               $result = $this->subject->isRegistered($this->notRegisteredIconIdentifier);
+               $this->assertEquals($result, FALSE);
+       }
+
+       /**
+        * @test
+        */
+       public function registerIconAddNewIconToRegistry() {
+               $unregisterdIcon = 'foo-bar-unregistered';
+               $this->assertFalse($this->subject->isRegistered($unregisterdIcon));
+               $this->subject->registerIcon($unregisterdIcon, FontawesomeIconProvider::class, array(
+                       'name' => 'pencil',
+                       'additionalClasses' => 'fa-fw'
+               ));
+               $this->assertTrue($this->subject->isRegistered($unregisterdIcon));
+       }
+
+       /**
+        * @expectedException \InvalidArgumentException
+        * @test
+        */
+       public function registerIconThrowsInvalidArgumentExceptionWithInvalidIconProvider() {
+               $this->subject->registerIcon($this->notRegisteredIconIdentifier, GeneralUtility::class);
+       }
+
+       /**
+        * @expectedException \TYPO3\CMS\Core\Exception
+        * @test
+        */
+       public function getIconConfigurationByIdentifierThrowsExceptionWithUnregisteredIconIdentifier() {
+               $this->subject->getIconConfigurationByIdentifier($this->notRegisteredIconIdentifier);
+       }
+
+       /**
+        * @test
+        */
+       public function getIconConfigurationByIdentifierReturnsCorrectConfiguration() {
+               $result = $this->subject->getIconConfigurationByIdentifier('default-not-found');
+               // result must contain at least provider and options array
+               $this->assertArrayHasKey('provider', $result);
+               $this->assertArrayHasKey('options', $result);
+               // the provider must implement the IconProviderInterface
+               $this->assertTrue(in_array(IconProviderInterface::class, class_implements($result['provider'])));
+       }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Imaging/IconTest.php b/typo3/sysext/core/Tests/Unit/Imaging/IconTest.php
new file mode 100644 (file)
index 0000000..86354ba
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Imaging;
+
+/*
+ * 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\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+
+/**
+ * Testcase for \TYPO3\CMS\Core\Imaging\Icon
+ */
+class IconTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\Imaging\Icon
+        */
+       protected $subject = NULL;
+
+       /**
+        * @var string
+        */
+       protected $iconIdentifier = 'actions-document-close';
+
+       /**
+        * @var string
+        */
+       protected $overlayIdentifier = 'overlay-read-only';
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $iconFactory = new IconFactory();
+               $this->subject = $iconFactory->getIcon($this->iconIdentifier, Icon::SIZE_SMALL, $this->overlayIdentifier);
+       }
+
+       /**
+        * @test
+        */
+       public function renderAndCastToStringReturnsTheSameCode() {
+               $this->assertEquals($this->subject->render(), (string)$this->subject);
+       }
+
+       /**
+        * @test
+        */
+       public function getIdentifierReturnsCorrectIdentifier() {
+               $this->assertEquals($this->iconIdentifier, $this->subject->getIdentifier());
+       }
+
+       /**
+        * @test
+        */
+       public function getOverlayIdentifierReturnsCorrectIdentifier() {
+               $this->assertEquals($this->overlayIdentifier, $this->subject->getOverlayIcon()->getIdentifier());
+       }
+
+       /**
+        * @test
+        */
+       public function getSizedentifierReturnsCorrectIdentifier() {
+               $this->assertEquals(Icon::SIZE_SMALL, $this->subject->getSize());
+       }
+}
index bd5d46c..611ec11 100644 (file)
@@ -234,7 +234,7 @@ class UriBuilderTest extends UnitTestCase {
                $_POST['foo2'] = 'bar2';
                $this->uriBuilder->setAddQueryString(TRUE);
                $this->uriBuilder->setAddQueryStringMethod('GET,POST');
-               $expectedResult = PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar&foo2=bar2';
+               $expectedResult = 'typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar&foo2=bar2';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -248,7 +248,7 @@ class UriBuilderTest extends UnitTestCase {
                $_POST['foo2'] = 'bar2';
                $this->uriBuilder->setAddQueryString(TRUE);
                $this->uriBuilder->setAddQueryStringMethod(NULL);
-               $expectedResult = PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar';
+               $expectedResult = 'typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -271,7 +271,7 @@ class UriBuilderTest extends UnitTestCase {
                                        'M',
                                        'id'
                                ),
-                               PATH_typo3 . 'index.php?moduleToken=dummyToken&foo=bar&foo2=bar2'
+                               'typo3/index.php?moduleToken=dummyToken&foo=bar&foo2=bar2'
                        ),
                        'Arguments to be excluded in the end' => array(
                                array(
@@ -286,7 +286,7 @@ class UriBuilderTest extends UnitTestCase {
                                        'M',
                                        'id'
                                ),
-                               PATH_typo3 . 'index.php?moduleToken=dummyToken&foo=bar&foo2=bar2'
+                               'typo3/index.php?moduleToken=dummyToken&foo=bar&foo2=bar2'
                        ),
                        'Arguments in nested array to be excluded' => array(
                                array(
@@ -303,7 +303,7 @@ class UriBuilderTest extends UnitTestCase {
                                        'id',
                                        'tx_foo[bar]'
                                ),
-                               PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
+                               'typo3/index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
                        ),
                        'Arguments in multidimensional array to be excluded' => array(
                                array(
@@ -322,7 +322,7 @@ class UriBuilderTest extends UnitTestCase {
                                        'id',
                                        'tx_foo[bar][baz]'
                                ),
-                               PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
+                               'typo3/index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
                        ),
                );
        }
@@ -350,7 +350,7 @@ class UriBuilderTest extends UnitTestCase {
         */
        public function buildBackendUriKeepsModuleQueryParametersIfAddQueryStringIsNotSet() {
                GeneralUtility::_GETset(array('M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar'));
-               $expectedResult = PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&id=pageId';
+               $expectedResult = 'typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -361,7 +361,7 @@ class UriBuilderTest extends UnitTestCase {
        public function buildBackendUriMergesAndOverrulesQueryParametersWithArguments() {
                GeneralUtility::_GETset(array('M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar'));
                $this->uriBuilder->setArguments(array('M' => 'overwrittenModuleKey', 'somePrefix' => array('bar' => 'baz')));
-               $expectedResult = PATH_typo3 . 'index.php?M=overwrittenModuleKey&moduleToken=dummyToken&id=pageId&somePrefix%5Bbar%5D=baz';
+               $expectedResult = 'typo3/index.php?M=overwrittenModuleKey&moduleToken=dummyToken&id=pageId&somePrefix%5Bbar%5D=baz';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -374,7 +374,7 @@ class UriBuilderTest extends UnitTestCase {
                $mockDomainObject = $this->getAccessibleMock(AbstractEntity::class, array('dummy'));
                $mockDomainObject->_set('uid', '123');
                $this->uriBuilder->setArguments(array('somePrefix' => array('someDomainObject' => $mockDomainObject)));
-               $expectedResult = PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken&somePrefix%5BsomeDomainObject%5D=123';
+               $expectedResult = 'typo3/index.php?M=moduleKey&moduleToken=dummyToken&somePrefix%5BsomeDomainObject%5D=123';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -385,7 +385,7 @@ class UriBuilderTest extends UnitTestCase {
        public function buildBackendUriRespectsSection() {
                GeneralUtility::_GETset(array('M' => 'moduleKey'));
                $this->uriBuilder->setSection('someSection');
-               $expectedResult = PATH_typo3 . 'index.php?M=moduleKey&moduleToken=dummyToken#someSection';
+               $expectedResult = 'typo3/index.php?M=moduleKey&moduleToken=dummyToken#someSection';
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -431,7 +431,7 @@ class UriBuilderTest extends UnitTestCase {
                );
                $this->uriBuilder->setAddQueryString(TRUE);
                $this->uriBuilder->setAddQueryStringMethod('POST,GET');
-               $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl(PATH_typo3 . 'index.php?moduleToken=dummyToken&key1=POST1&key2=GET2&key3[key31]=POST31&key3[key32]=GET32&key3[key33][key331]=GET331&key3[key33][key332]=POST332');
+               $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('typo3/index.php?moduleToken=dummyToken&key1=POST1&key2=GET2&key3[key31]=POST31&key3[key32]=GET32&key3[key33][key331]=GET331&key3[key33][key332]=POST332');
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
@@ -463,7 +463,7 @@ class UriBuilderTest extends UnitTestCase {
                );
                $this->uriBuilder->setAddQueryString(TRUE);
                $this->uriBuilder->setAddQueryStringMethod('GET,POST');
-               $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl(PATH_typo3 . 'index.php?moduleToken=dummyToken&key1=GET1&key2=POST2&key3[key31]=GET31&key3[key32]=POST32&key3[key33][key331]=POST331&key3[key33][key332]=GET332');
+               $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('typo3/index.php?moduleToken=dummyToken&key1=GET1&key2=POST2&key3[key31]=GET31&key3[key32]=POST32&key3[key33][key331]=POST331&key3[key33][key332]=GET332');
                $actualResult = $this->uriBuilder->buildBackendUri();
                $this->assertEquals($expectedResult, $actualResult);
        }
index 187d449..79957a6 100644 (file)
@@ -7528,6 +7528,72 @@ button.close {
 .callout-notice .media .fa-stack > .fa:first-child {
   color: #a0a0a0;
 }
+.icon {
+  position: relative;
+  display: inline-block;
+  overflow: hidden;
+}
+.icon img {
+  display: block;
+  height: 100%;
+  width: 100%;
+}
+.icon-markup {
+  position: absolute;
+  height: 100%;
+  width: 100%;
+  text-align: center;
+}
+.icon-overlay {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  height: 50%;
+  width: 50%;
+  text-align: center;
+}
+.icon-size-small {
+  height: 16px;
+  width: 16px;
+}
+.icon-size-small .icon-unify {
+  line-height: 16px;
+  font-size: 14px;
+}
+.icon-size-small .icon-overlay .icon-unify {
+  line-height: 8px;
+  font-size: 7px;
+}
+.icon-size-default {
+  height: 32px;
+  width: 32px;
+}
+.icon-size-default .icon-unify {
+  line-height: 32px;
+  font-size: 28px;
+}
+.icon-size-default .icon-overlay .icon-unify {
+  line-height: 16px;
+  font-size: 14px;
+}
+.icon-size-large {
+  height: 48px;
+  width: 48px;
+}
+.icon-size-large .icon-unify {
+  line-height: 48px;
+  font-size: 42px;
+}
+.icon-size-large .icon-overlay .icon-unify {
+  line-height: 24px;
+  font-size: 21px;
+}
+.icon-default-not-found {
+  color: #c83c3c;
+}
+.icon-overlay-read-only {
+  color: #c83c3c;
+}
 .clearfix:before,
 .clearfix:after,
 .dl-horizontal dd:before,