[FEATURE] Introduce AvatarProvider 80/41780/13
authorFrans Saris <franssaris@gmail.com>
Tue, 11 Aug 2015 19:35:35 +0000 (21:35 +0200)
committerAndreas Fernandez <typo3@scripting-base.de>
Thu, 13 Aug 2015 14:58:01 +0000 (16:58 +0200)
To make providing a avatar image for BE users more flexible a proper API
is needed to be able to register different AvatarProviders.

Releases: master
Resolves: #68429
Change-Id: Icfe9df0a82f1d812ca70af1fa1ddee506d1de583
Reviewed-on: http://review.typo3.org/41780
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
typo3/sysext/backend/Classes/Backend/Avatar/Avatar.php
typo3/sysext/backend/Classes/Backend/Avatar/AvatarProviderInterface.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Backend/Avatar/Image.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Backend/Avatar/ImageProvider.php [new file with mode: 0644]
typo3/sysext/backend/ext_localconf.php
typo3/sysext/core/Documentation/Changelog/master/Feature-68429-IntroducedAvatarProviderAPI.rst [new file with mode: 0644]
typo3/sysext/setup/Classes/Controller/SetupModuleController.php

index fe94c9a..fa5a6a8 100644 (file)
@@ -14,26 +14,33 @@ namespace TYPO3\CMS\Backend\Backend\Avatar;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
-use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
-use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
- * Default Avatar class
+ * Avatar renderer class
  */
 class Avatar {
 
        /**
-        * @var array
+        * Array of sorted and initiated avatar providers
+        *
+        * @var AvatarProviderInterface[]
         */
-       protected $defaultConfiguration = array();
+       protected $avatarProviders = [];
+
+       /**
+        * Construct
+        */
+       public function __construct() {
+               $this->validateSortAndInitiateAvatarProviders();
+       }
 
        /**
         * Render avatar tag
         *
-        * @param array $backendUser
+        * @param array $backendUser be_user record
         * @param int $size width and height of the image
         * @param bool $showIcon show the record icon
         * @return string
@@ -43,50 +50,87 @@ class Avatar {
                        $backendUser = $this->getBackendUser()->user;
                }
 
-               $fileUid = $this->getAvatarFileUid($backendUser['uid']);
-
-               // Get file object
-               try {
-                       $file = ResourceFactory::getInstance()->getFileObject($fileUid);
-                       $processedImage = $file->process(
-                               ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
-                               array('width' => $size . 'c', 'height' => $size . 'c')
-                       );
-                       $imageUri = $processedImage->getPublicUrl(TRUE);
-
-                       // Image
-                       $image = '<img src="' . htmlspecialchars($imageUri) . '"' .
-                               'width="' . $processedImage->getProperty('width') . '" ' .
-                               'height="' . $processedImage->getProperty('height') . '">';
-               } catch (FileDoesNotExistException $e) {
-                       // don't show an image
-                       $image = '';
-               }
-
                // Icon
                $icon = '';
                if ($showIcon) {
                        $icon = '<span class="avatar-icon">' . IconUtility::getSpriteIconForRecord('be_users', $backendUser) . '</span>';
                }
 
+               $image = $this->getImgTag($backendUser, $size);
+
                return '<span class="avatar"><span class="avatar-image">' . $image . '</span>' . $icon . '</span>';
        }
 
        /**
-        * Get Avatar fileUid
+        * Get avatar img tag
         *
-        * @param int $beUserId
-        * @return int
+        * @param array $backendUser be_user record
+        * @param int $size
+        * @return string
         */
-       protected function getAvatarFileUid($beUserId) {
-               $file = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
-                       'uid_local',
-                       'sys_file_reference',
-                       'tablenames = \'be_users\' AND fieldname = \'avatar\' AND ' .
-                       'table_local = \'sys_file\' AND uid_foreign = ' . (int)$beUserId .
-                       BackendUtility::BEenableFields('sys_file_reference') . BackendUtility::deleteClause('sys_file_reference')
-               );
-               return $file ? $file['uid_local'] : 0;
+       public function getImgTag(array $backendUser = NULL, $size = 32) {
+               if (!is_array($backendUser)) {
+                       $backendUser = $this->getBackendUser()->user;
+               }
+
+               $imageTag = '';
+               $avatarImage = $this->getImage($backendUser, $size);
+
+               if ($avatarImage) {
+                       $imageTag = '<img src="' . htmlspecialchars($avatarImage->getUrl()) . '"' .
+                               'width="' . (int)$avatarImage->getWidth() . '" ' .
+                               'height="' . (int)$avatarImage->getHeight() . '" />';
+               }
+
+               return $imageTag;
+       }
+
+       /**
+        * Get Image from first provider that returns one
+        *
+        * @param array $backendUser be_user record
+        * @param int $size
+        * @return Image|NULL
+        */
+       public function getImage(array $backendUser, $size) {
+               foreach ($this->avatarProviders as $provider) {
+                       $avatarImage = $provider->getImage($backendUser, $size);
+                       if (!empty($avatarImage)) {
+                               return $avatarImage;
+                       }
+               }
+               return NULL;
+       }
+
+       /**
+        * Validates the registered avatar providers
+        *
+        * @return void
+        * @throws \RuntimeException
+        */
+       protected function validateSortAndInitiateAvatarProviders() {
+               if (
+                       empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders'])
+                       || !is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders'])
+               ) {
+                       return;
+               }
+               $providers = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders'];
+               foreach ($providers as $identifier => $configuration) {
+                       if (empty($configuration) || !is_array($configuration)) {
+                               throw new \RuntimeException('Missing configuration for avatar provider "' . $identifier . '".', 1439317801);
+                       }
+                       if (!is_string($configuration['provider']) || empty($configuration['provider']) || !class_exists($configuration['provider']) || !is_subclass_of($configuration['provider'], AvatarProviderInterface::class)) {
+                               throw new \RuntimeException('The avatar provider "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . AvatarProviderInterface::class . '".', 1439317802);
+                       }
+               }
+
+               $orderedProviders = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($providers);
+
+               // Initiate providers
+               foreach ($orderedProviders as $configuration) {
+                       $this->avatarProviders[] = GeneralUtility::makeInstance($configuration['provider']);
+               }
        }
 
        /**
@@ -98,10 +142,4 @@ class Avatar {
                return $GLOBALS['BE_USER'];
        }
 
-       /**
-        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
-        */
-       protected function getDatabaseConnection() {
-               return $GLOBALS['TYPO3_DB'];
-       }
 }
diff --git a/typo3/sysext/backend/Classes/Backend/Avatar/AvatarProviderInterface.php b/typo3/sysext/backend/Classes/Backend/Avatar/AvatarProviderInterface.php
new file mode 100644 (file)
index 0000000..bd826b3
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace TYPO3\CMS\Backend\Backend\Avatar;
+
+/*
+ * 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!
+ */
+
+/**
+ * Class AvatarProviderInterface
+ */
+interface AvatarProviderInterface {
+
+       /**
+        * Get Image
+        *
+        * @param array $backendUser be_user record
+        * @param int $size
+        * @return Image|NULL
+        */
+       public function getImage(array $backendUser, $size);
+
+}
diff --git a/typo3/sysext/backend/Classes/Backend/Avatar/Image.php b/typo3/sysext/backend/Classes/Backend/Avatar/Image.php
new file mode 100644 (file)
index 0000000..1e8425d
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+namespace TYPO3\CMS\Backend\Backend\Avatar;
+
+/*
+ * 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!
+ */
+
+/**
+ * Class Image
+ *
+ * Holds url + dimensions of avatar image
+ */
+class Image {
+
+       /**
+        * Url of avatar image. Needs to be relative to the /typo3/ folder or an absolute URL.
+        *
+        * @var string
+        */
+       protected $url;
+
+       /**
+        * @var int
+        */
+       protected $width;
+
+       /**
+        * @var int
+        */
+       protected $height;
+
+       /**
+        * Constructor
+        *
+        * @param string $url url of image. Needs to be relative to the /typo3/ folder or an absolute URL.
+        * @param int $width width of image
+        * @param int $height height of image
+        */
+       public function __construct($url, $width, $height) {
+               $this->url = $url;
+               $this->width = (int)$width;
+               $this->height = (int)$height;
+       }
+
+       /**
+        * Get url
+        *
+        * @return string
+        */
+       public function getUrl() {
+               return $this->url;
+       }
+
+       /**
+        * Get width
+        *
+        * @return int
+        */
+       public function getWidth() {
+               return $this->width;
+       }
+
+       /**
+        * Get height
+        *
+        * @return int
+        */
+       public function getHeight() {
+               return $this->height;
+       }
+}
diff --git a/typo3/sysext/backend/Classes/Backend/Avatar/ImageProvider.php b/typo3/sysext/backend/Classes/Backend/Avatar/ImageProvider.php
new file mode 100644 (file)
index 0000000..4b62d0a
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+namespace TYPO3\CMS\Backend\Backend\Avatar;
+
+/*
+ * 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\Utility\BackendUtility;
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class ImageProvider
+ */
+class ImageProvider implements AvatarProviderInterface {
+
+       /**
+        * Get Image
+        *
+        * @param array $backendUser be_user record
+        * @param int $size
+        * @return Image|NULL
+        */
+       public function getImage(array $backendUser, $size) {
+               $fileUid = $this->getAvatarFileUid($backendUser['uid']);
+
+               // Get file object
+               try {
+                       $file = ResourceFactory::getInstance()->getFileObject($fileUid);
+                       $processedImage = $file->process(
+                               ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
+                               array('width' => $size . 'c', 'height' => $size . 'c')
+                       );
+
+                       $image = GeneralUtility::makeInstance(
+                               Image::class,
+                               $processedImage->getPublicUrl(TRUE),
+                               $processedImage->getProperty('width'),
+                               $processedImage->getProperty('height')
+                       );
+
+               } catch (FileDoesNotExistException $e) {
+                       // No image found
+                       $image = NULL;
+               }
+
+               return $image;
+       }
+
+       /**
+        * Get Avatar fileUid
+        *
+        * @param int $beUserId
+        * @return int
+        */
+       protected function getAvatarFileUid($beUserId) {
+               $file = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
+                       'uid_local',
+                       'sys_file_reference',
+                       'tablenames = \'be_users\' AND fieldname = \'avatar\' AND ' .
+                       'table_local = \'sys_file\' AND uid_foreign = ' . (int)$beUserId .
+                       BackendUtility::BEenableFields('sys_file_reference') . BackendUtility::deleteClause('sys_file_reference')
+               );
+               return $file ? $file['uid_local'] : 0;
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+}
index bfc4c82..e107e78 100644 (file)
@@ -22,6 +22,10 @@ if (TYPO3_MODE === 'BE') {
                'icon-class' => 'fa-key',
                'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:login.link'
        );
+
+       $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']['imageProvider'] = array(
+               'provider' => \TYPO3\CMS\Backend\Backend\Avatar\ImageProvider::class
+       );
 }
 
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default'] = \TYPO3\CMS\Core\FrontendEditing\FrontendEditingController::class;
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-68429-IntroducedAvatarProviderAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-68429-IntroducedAvatarProviderAPI.rst
new file mode 100644 (file)
index 0000000..c2024c9
--- /dev/null
@@ -0,0 +1,61 @@
+===============================================
+Feature: #68429 - Introduced AvatarProvider API
+===============================================
+
+Description
+===========
+
+To make providing a avatar image for BE users more flexible a API it introduced so you can register AvatarProviders.
+The core provides the ``ImageProvider`` by default.
+
+When an avatar is rendered in the BE the available AvatarProviders are asked if they can provide an
+``TYPO3\CMS\Backend\Backend\Image`` for given ``be_user`` and requested size. The first ``TYPO3\CMS\Backend\Backend\Image``
+that gets returned is used.
+
+Registering a avatar provider
+-----------------------------
+
+A avatar provider can be registered within your ``ext_localconf.php`` file like this:
+
+.. code-block:: php
+
+       $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']['myCustomProvider'] = [
+               'provider' => \MyVendor\MyExtension\AvatarProvider\CompanyAvatarProvider::class,
+               'before' => ['imageProvider']
+       ];
+
+The settings are defined as:
+
+* ``provider``: The avatar provider class name, which must implement ``TYPO3\CMS\Backend\Backend\AvatarProviderInterface``.
+* ``before``/``after``: You can define the ordering how providers are executed. This is order is used as fallback of the avatar providers. Each property must be an array of provider names.
+
+
+For a new avatar provider you have to register a **new key** in ``$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']``.
+If your avatar provider extends another one, you may only overwrite necessary settings. An example would be to
+extend an existing provider and replace its registered 'provider' class with your new class name.
+
+.. code-block:: php
+
+       $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']['imageProvider']['provider'] = \MyVendor\MyExtension\AvatarProvider\CustomImageProvider::class
+
+
+AvatarProviderInterface
+-----------------------
+
+The AvatarProviderInterface contains only one method:
+
+``public function getImage(array $backendUser, $size);``
+
+The parameters are defined as:
+
+* ``$backendUser``: The record of from ``be_user`` database table.
+* ``$size``: The requested size of the avatar image.
+
+The return value of the method is expected to be and ``TYPO3\CMS\Backend\Backend\Image`` instance or NULL when the
+provider can not provide an image.
+
+An ``TYPO3\CMS\Backend\Backend\Image`` object holds 3 properties:
+
+* ``$url``: Url of avatar image. Needs to be relative to the /typo3/ folder or an absolute URL.
+* ``$width``: The width of the image.
+* ``$height``: The height of the image.
index 3e7de6b..de0afe7 100644 (file)
@@ -14,7 +14,7 @@ namespace TYPO3\CMS\Setup\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
+use TYPO3\CMS\Backend\Backend\Avatar\ImageProvider;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@@ -605,19 +605,26 @@ class SetupModuleController {
                                        $avatarFileUid = $this->getAvatarFileUid($this->getBackendUser()->user['uid']);
 
                                        if ($avatarFileUid) {
-                                               $avatar = GeneralUtility::makeInstance(Avatar::class);
-                                               $icon = $avatar->render();
-                                               $html .= '<span class="pull-left" style="padding-right: 10px" id="image_' . $fieldName . '">' . $icon . ' </span>';
+                                               $imageProvider = GeneralUtility::makeInstance(ImageProvider::class);
+                                               $avatarImage = $imageProvider->getImage($this->getBackendUser()->user, 32);
+                                               if ($avatarImage) {
+                                                       $icon = '<span class="avatar"><span class="avatar-image">' .
+                                                               '<img src="' . htmlspecialchars($avatarImage->getUrl()) . '"' .
+                                                               'width="' . (int)$avatarImage->getWidth() . '" ' .
+                                                               'height="' . (int)$avatarImage->getHeight() . '" />' .
+                                                               '</span></span>';
+                                                       $html .= '<span class="pull-left" style="padding-right: 10px" id="image_' . htmlspecialchars($fieldName) . '">' . $icon . ' </span>';
+                                               }
                                        }
-                                       $html .= '<input id="field_' . $fieldName . '" type="hidden" ' .
-                                                       'name="data' . $dataAdd . '[' . $fieldName . ']"' . $more .
+                                       $html .= '<input id="field_' . htmlspecialchars($fieldName) . '" type="hidden" ' .
+                                                       'name="data' . $dataAdd . '[' . htmlspecialchars($fieldName) . ']"' . $more .
                                                        ' value="' . $avatarFileUid . '" />';
 
                                        $html .= '<div class="btn-group">';
                                        if ($avatarFileUid) {
-                                               $html .= '<a id="clear_button_' . $fieldName . '" onclick="clearExistingImage(); return false;" class="btn btn-default"><span class="t3-icon fa t3-icon fa fa-remove"> </span></a>';
+                                               $html .= '<a id="clear_button_' . htmlspecialchars($fieldName) . '" onclick="clearExistingImage(); return false;" class="btn btn-default"><span class="t3-icon fa t3-icon fa fa-remove"> </span></a>';
                                        }
-                                       $html .= '<a id="add_button_' . $fieldName . '" class="btn btn-default btn-add-avatar" onclick="openFileBrowser();return false;"><span class="t3-icon t3-icon-actions t3-icon-actions-insert t3-icon-insert-record"> </span></a>' .
+                                       $html .= '<a id="add_button_' . htmlspecialchars($fieldName) . '" class="btn btn-default btn-add-avatar" onclick="openFileBrowser();return false;"><span class="t3-icon t3-icon-actions t3-icon-actions-insert t3-icon-insert-record"> </span></a>' .
                                                        '</div>';
 
                                        $this->addAvatarButtonJs($fieldName);
@@ -942,15 +949,15 @@ class SetupModuleController {
                        }
 
                        function clearExistingImage() {
-                               TYPO3.jQuery(\'#image_' . $fieldName . '\').hide();
-                               TYPO3.jQuery(\'#clear_button_' . $fieldName . '\').hide();
-                               TYPO3.jQuery(\'#field_' . $fieldName . '\').val(\'\');
+                               TYPO3.jQuery(\'#image_' . htmlspecialchars($fieldName) . '\').hide();
+                               TYPO3.jQuery(\'#clear_button_' . htmlspecialchars($fieldName) . '\').hide();
+                               TYPO3.jQuery(\'#field_' . htmlspecialchars($fieldName) . '\').val(\'\');
                        }
 
                        function setFileUid(field, value, fileUid) {
                                clearExistingImage();
-                               TYPO3.jQuery(\'#field_' . $fieldName . '\').val(fileUid);
-                               TYPO3.jQuery(\'#add_button_' . $fieldName . '\').removeClass(\'btn-default\').addClass(\'btn-info\');
+                               TYPO3.jQuery(\'#field_' . htmlspecialchars($fieldName) . '\').val(fileUid);
+                               TYPO3.jQuery(\'#add_button_' . htmlspecialchars($fieldName) . '\').removeClass(\'btn-default\').addClass(\'btn-info\');
 
                                browserWin.close();
                        }