[FEATURE] Detect file icons by mime-type 39/43039/24
authorFrans Saris <franssaris@gmail.com>
Fri, 4 Sep 2015 17:18:47 +0000 (19:18 +0200)
committerMorton Jonuschat <m.jonuschat@mojocode.de>
Fri, 16 Oct 2015 09:40:07 +0000 (11:40 +0200)
Recognize files by the main part of the mime-type, provide icons for
the main parts audio, video, image and text. Allow registration and
overwriting of such mappings of file icons by mime-type.

Releases: master
Resolves: #69764
Change-Id: I9cc15157df2416c463e2105cc4bfb88a205e73b6
Reviewed-on: http://review.typo3.org/43039
Reviewed-by: Daniel Goerz <ervaude@gmail.com>
Tested-by: Daniel Goerz <ervaude@gmail.com>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
typo3/sysext/core/Classes/Imaging/IconFactory.php
typo3/sysext/core/Classes/Imaging/IconRegistry.php
typo3/sysext/core/Documentation/Changelog/master/Feature-69764-IntroducedFileIconDetectionByMimeType.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Imaging/IconFactoryTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconRegistryTest.php

index a10b1dd..2349bc3 100644 (file)
@@ -239,15 +239,13 @@ class IconFactory
         }
 
         krsort($recordType);
-        /** @var IconRegistry $iconRegistry */
-        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
         foreach ($recordType as $iconName) {
-            if ($iconRegistry->isRegistered($iconName)) {
+            if ($this->iconRegistry->isRegistered($iconName)) {
                 return $iconName;
             }
         }
 
-        return 'default-not-found';
+        return $this->iconRegistry->getDefaultIconIdentifier();
     }
 
     /**
@@ -335,8 +333,7 @@ class IconFactory
      */
     public function getIconForFileExtension($fileExtension, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null)
     {
-        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
-        $iconName = $iconRegistry->getIconIdentifierForFileExtension($fileExtension);
+        $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
         return $this->getIcon($iconName, $size, $overlayIdentifier);
     }
 
@@ -409,13 +406,33 @@ class IconFactory
                 }
             }
 
-            // File
-        } else {
-            if ($resource instanceof File && $resource->isMissing()) {
+        // File
+        } elseif ($resource instanceof File) {
+            $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
+
+            // Check if we find a exact matching mime type
+            if ($mimeTypeIcon !== null) {
+                $iconIdentifier = $mimeTypeIcon;
+            } else {
+                $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
+                if ($fileExtensionIcon !== 'mimetypes-other-other') {
+                    // Fallback 1: icon by file extension
+                    $iconIdentifier = $fileExtensionIcon;
+                } else {
+                    // Fallback 2: icon by mime type with subtype replaced by *
+                    $mimeTypeParts = explode('/', $resource->getMimeType());
+                    $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
+                    if ($mimeTypeIcon !== null) {
+                        $iconIdentifier = $mimeTypeIcon;
+                    } else {
+                        // Fallback 3: use 'mimetypes-other-other'
+                        $iconIdentifier = $fileExtensionIcon;
+                    }
+                }
+            }
+            if ($resource->isMissing()) {
                 $overlayIdentifier = 'overlay-missing';
             }
-            $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
-            $iconIdentifier = $iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
         }
 
         unset($options['mount-root']);
index 099c628..4260c2a 100644 (file)
@@ -1854,6 +1854,18 @@ class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface
     );
 
     /**
+     * Mapping of mime types to icons
+     *
+     * @var string[]
+     */
+    protected $mimeTypeMapping = array(
+        'video/*' => 'mimetypes-media-video',
+        'audio/*' => 'mimetypes-media-audio',
+        'image/*' => 'mimetypes-media-image',
+        'text/*' => 'mimetypes-text-text',
+    );
+
+    /**
      * Array of deprecated icons, add deprecated icons to this array and remove it from registry
      * - Index of this array contains the deprecated icon
      * - Value of each entry must contain the deprecation message and can contain an identifier which replaces the old identifier
@@ -1940,7 +1952,7 @@ class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
-     * Registers an icon for a file extension.
+     * Register an icon for a file extension
      *
      * @param string $fileExtension
      * @param string $iconIdentifier
@@ -1951,6 +1963,17 @@ class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
+     * Register an icon for a mime-type
+     *
+     * @param string $mimeType
+     * @param string $iconIdentifier
+     */
+    public function registerMimeTypeIcon($mimeType, $iconIdentifier)
+    {
+        $this->mimeTypeMapping[$mimeType] = $iconIdentifier;
+    }
+
+    /**
      * Fetches the configuration provided by registerIcon()
      *
      * @param string $identifier the icon identifier
@@ -2017,6 +2040,20 @@ class IconRegistry implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
+     * Get iconIdentifier for given mimeType
+     *
+     * @param string $mimeType
+     * @return string|null Returns null if no icon is registered for the mimeType
+     */
+    public function getIconIdentifierForMimeType($mimeType)
+    {
+        if (!isset($this->mimeTypeMapping[$mimeType])) {
+            return null;
+        }
+        return $this->mimeTypeMapping[$mimeType];
+    }
+
+    /**
      * Load icons from TCA for each table and add them as "tcarecords-XX" to $this->icons
      */
     protected function registerTCAIcons()
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-69764-IntroducedFileIconDetectionByMimeType.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-69764-IntroducedFileIconDetectionByMimeType.rst
new file mode 100644 (file)
index 0000000..99b2695
--- /dev/null
@@ -0,0 +1,21 @@
+=============================================================
+Feature: #69764 - Introduced file icon detection by mime-type
+=============================================================
+
+Description
+===========
+
+The IconRegistry has been extended with a mapping of file icons by mime-type. It is possible to register full mime-types
+``main-type/sub-type`` but also a fallback for only the main part of the mime-type ``main-type/*``.
+The core provides these fallbacks for ``audio/*``, ``video/*``, ``image/*`` and ``text/*``.
+
+
+Impact
+======
+
+It is now possible to register or overwrite the iconIdentifier for a file mime-type.
+
+.. code-block:: php
+
+       $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
+       $iconRegistry->registerMimeTypeIcon('video/my-custom-type', 'icon-identifier-for-my-custom-type');
index 6970b41..99cb4a7 100644 (file)
@@ -82,8 +82,17 @@ class IconFactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $this->iconRegistryMock = $this->prophesize(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
         $this->subject = new IconFactory($this->iconRegistryMock->reveal());
 
+        $this->iconRegistryMock->isRegistered('tcarecords--default')->willReturn(false);
         $this->iconRegistryMock->isRegistered(Argument::any())->willReturn(true);
         $this->iconRegistryMock->isDeprecated(Argument::any())->willReturn(false);
+        $this->iconRegistryMock->getDefaultIconIdentifier(Argument::any())->willReturn('default-not-found');
+        $this->iconRegistryMock->getIconIdentifierForMimeType('application/pdf')->willReturn('mimetypes-pdf');
+        $this->iconRegistryMock->getIconIdentifierForMimeType('image/*')->willReturn('mimetypes-media-image');
+        $this->iconRegistryMock->getIconIdentifierForMimeType(Argument::any())->willReturn(null);
+        $this->iconRegistryMock->getIconIdentifierForFileExtension(Argument::any())->willReturn('mimetypes-other-other');
+        $this->iconRegistryMock->getIconIdentifierForFileExtension('foo')->willReturn('mimetypes-other-other');
+        $this->iconRegistryMock->getIconIdentifierForFileExtension('pdf')->willReturn('mimetypes-pdf');
+        $this->iconRegistryMock->getIconIdentifierForFileExtension('png')->willReturn('mimetypes-media-image');
         $this->iconRegistryMock->getIconConfigurationByIdentifier(Argument::any())->willReturn([
             'provider' => FontawesomeIconProvider::class,
             'options' => array(
@@ -316,6 +325,7 @@ class IconFactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $resourceProphecy = $this->prophesize(File::class);
         $resourceProphecy->isMissing()->willReturn(false);
         $resourceProphecy->getExtension()->willReturn('pdf');
+        $resourceProphecy->getMimeType()->willReturn('');
 
         $this->assertContains('<span class="icon icon-size-default icon-state-default icon-mimetypes-pdf">',
             $this->subject->getIconForResource($resourceProphecy->reveal())->render());
@@ -361,13 +371,37 @@ class IconFactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     }
 
     /**
+     * Tests the returns of file pdf with known mime-type
+     *
+     * @test
+     */
+    public function getIconForResourceWithMimeTypeApplicationPdfReturnsPdfIcon()
+    {
+        $fileObject = $this->getTestSubjectFileObject('pdf', 'application/pdf');
+        $result = $this->subject->getIconForResource($fileObject)->render();
+        $this->assertContains('<span class="icon icon-size-default icon-state-default icon-mimetypes-pdf">', $result);
+    }
+
+    /**
+     * Tests the returns of file with custom image mime-type
+     *
+     * @test
+     */
+    public function getIconForResourceWithCustomImageMimeTypeReturnsImageIcon()
+    {
+        $fileObject = $this->getTestSubjectFileObject('custom', 'image/my-custom-extension');
+        $result = $this->subject->getIconForResource($fileObject)->render();
+        $this->assertContains('<span class="icon icon-size-default icon-state-default icon-mimetypes-media-image">', $result);
+    }
+
+    /**
      * Tests the returns of file png
      *
      * @test
      */
     public function getIconForResourceWithPngFileReturnsIcon()
     {
-        $fileObject = $this->getTestSubjectFileObject('png');
+        $fileObject = $this->getTestSubjectFileObject('png', 'image/png');
         $result = $this->subject->getIconForResource($fileObject)->render();
         $this->assertContains('<span class="icon icon-size-default icon-state-default icon-mimetypes-media-image">', $result);
     }
@@ -530,14 +564,16 @@ class IconFactoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     /**
      * Create file object to use as test subject
      *
-     * @param $extension
+     * @param string $extension
+     * @param string $mimeType
      * @return \TYPO3\CMS\Core\Resource\File
      */
-    protected function getTestSubjectFileObject($extension)
+    protected function getTestSubjectFileObject($extension, $mimeType = '')
     {
         $mockedStorage = $this->getMock(\TYPO3\CMS\Core\Resource\ResourceStorage::class, array(), array(), '', false);
         $mockedFile = $this->getMock(\TYPO3\CMS\Core\Resource\File::class, array(), array(array(), $mockedStorage));
-        $mockedFile->expects($this->once())->method('getExtension')->will($this->returnValue($extension));
+        $mockedFile->expects($this->atMost(1))->method('getExtension')->will($this->returnValue($extension));
+        $mockedFile->expects($this->atLeastOnce())->method('getMimeType')->will($this->returnValue($mimeType));
         return $mockedFile;
     }
 
index bbddfea..c064ea4 100644 (file)
@@ -179,4 +179,33 @@ class IconRegistryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $result = $this->subject->getIconIdentifierForFileExtension('jpg');
         $this->assertEquals('xyz', $result);
     }
+
+    /**
+     * @test
+     */
+    public function registerMimeTypeIconRegisterAnIcon()
+    {
+        $this->subject->registerMimeTypeIcon('foo/bar', 'mimetype-foo-bar');
+        $result = $this->subject->getIconIdentifierForMimeType('foo/bar');
+        $this->assertEquals('mimetype-foo-bar', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function registerMimeTypeIconOverwriteAnExistingIcon()
+    {
+        $this->subject->registerMimeTypeIcon('video/*', 'mimetype-foo-bar');
+        $result = $this->subject->getIconIdentifierForMimeType('video/*');
+        $this->assertEquals('mimetype-foo-bar', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function getIconIdentifierForMimeTypeWithUnknowMimeTypeReturnNull()
+    {
+        $result = $this->subject->getIconIdentifierForMimeType('bar/foo');
+        $this->assertEquals(null, $result);
+    }
 }