‪TYPO3CMS  11.5
IconFactory.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\Container\ContainerInterface;
19 use Psr\EventDispatcher\EventDispatcherInterface;
28 
34 {
38  protected ‪$iconRegistry;
39 
46  protected ‪$recordStatusMapping = [];
47 
54  protected ‪$overlayPriorities = [];
55 
61  protected static ‪$iconCache = [];
62 
66  protected ‪$eventDispatcher;
67 
68  protected ContainerInterface ‪$container;
69 
75  public function ‪__construct(EventDispatcherInterface ‪$eventDispatcher, ‪IconRegistry ‪$iconRegistry, ContainerInterface ‪$container)
76  {
77  $this->eventDispatcher = ‪$eventDispatcher;
78  $this->iconRegistry = ‪$iconRegistry;
79  $this->container = ‪$container;
80  $this->recordStatusMapping = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['recordStatusMapping'];
81  $this->overlayPriorities = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['overlayPriorities'];
82  }
83 
91  public function ‪getIcon($identifier, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null, ?‪IconState $state = null)
92  {
93  $cacheIdentifier = md5($identifier . $size . $overlayIdentifier . (string)$state);
94  if (!empty(static::$iconCache[$cacheIdentifier])) {
95  return static::$iconCache[$cacheIdentifier];
96  }
97 
98  if (
99  !$this->iconRegistry->isDeprecated($identifier)
100  && !$this->iconRegistry->isRegistered($identifier)
101  ) {
102  // in case icon identifier is neither deprecated nor registered
103  $identifier = $this->iconRegistry->getDefaultIconIdentifier();
104  }
105 
106  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
107  $iconConfiguration['state'] = $state;
108  $icon = $this->‪createIcon($identifier, $size, $overlayIdentifier, $iconConfiguration);
109 
111  $iconProvider = $this->container->has($iconConfiguration['provider']) ?
112  $this->container->get($iconConfiguration['provider']) :
113  GeneralUtility::makeInstance($iconConfiguration['provider']);
114  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
115 
116  static::$iconCache[$cacheIdentifier] = $icon;
117 
118  return $icon;
119  }
120 
129  public function ‪getIconForRecord($table, array $row, $size = ‪Icon::SIZE_DEFAULT)
130  {
131  $iconIdentifier = $this->‪mapRecordTypeToIconIdentifier($table, $row);
132  $overlayIdentifier = $this->‪mapRecordTypeToOverlayIdentifier($table, $row);
133  return $this->‪getIcon($iconIdentifier, $size, $overlayIdentifier);
134  }
135 
152  public function ‪mapRecordTypeToIconIdentifier($table, array $row)
153  {
154  $recordType = [];
155  $ref = null;
156 
157  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
158  $column = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
159  if (isset($row[$column])) {
160  // even if not properly documented the value of the typeicon_column in a record could be
161  // an array (multiselect) in typeicon_classes a key could consist of a comma-separated string "foo,bar"
162  // but mostly it should be only one entry in that array
163  if (is_array($row[$column])) {
164  $recordType[1] = implode(',', $row[$column]);
165  } else {
166  $recordType[1] = $row[$column];
167  }
168  } else {
169  $recordType[1] = 'default';
170  }
171  // Workaround to give nav_hide pages a complete different icon
172  // Although it's not a separate doctype
173  // and to give root-pages an own icon
174  if ($table === 'pages') {
175  if (($row['nav_hide'] ?? 0) > 0) {
176  $recordType[2] = $this->‪getRecordTypeForPageType(
177  $recordType[1],
178  'hideinmenu',
179  $table
180  );
181  }
182  if (($row['is_siteroot'] ?? 0) > 0) {
183  $recordType[3] = $this->‪getRecordTypeForPageType(
184  $recordType[1],
185  'root',
186  $table
187  );
188  }
189  if (!empty($row['module'])) {
190  if (is_array($row['module'])) {
191  // field 'module' is configured as type 'select' in the TCA,
192  // so the value may have already been converted to an array
193  $moduleSuffix = reset($row['module']);
194  } else {
195  $moduleSuffix = $row['module'];
196  }
197  $recordType[4] = 'contains-' . $moduleSuffix;
198  }
199  if (($row['content_from_pid'] ?? 0) > 0) {
200  if ($row['is_siteroot'] ?? false) {
201  $recordType[4] = $this->‪getRecordTypeForPageType(
202  $recordType[1],
203  'contentFromPid-root',
204  $table
205  );
206  } else {
207  $suffix = (int)$row['nav_hide'] === 0 ? 'contentFromPid' : 'contentFromPid-hideinmenu';
208  $recordType[4] = $this->‪getRecordTypeForPageType($recordType[1], $suffix, $table, 'page');
209  }
210  }
211  }
212  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
213  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
214  ) {
215  foreach ($recordType as $key => $type) {
216  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
217  $recordType[$key] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
218  } else {
219  unset($recordType[$key]);
220  }
221  }
222  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'] ?? '';
223  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])
224  && isset($row[$column]) && is_string($row[$column])
225  ) {
226  $recordType[5] = str_replace(
227  '###TYPE###',
228  $row[$column] ?? '',
229  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']
230  );
231  }
232  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
233  $parameters = ['row' => $row];
234  $recordType[6] = GeneralUtility::callUserFunction(
235  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'],
236  $parameters,
237  $ref
238  );
239  }
240  } else {
241  foreach ($recordType as &$type) {
242  $type = 'tcarecords-' . $table . '-' . $type;
243  }
244  unset($type);
245  $recordType[0] = 'tcarecords-' . $table . '-default';
246  }
247  } elseif (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
248  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
249  ) {
250  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
251  } else {
252  $recordType[0] = 'tcarecords-' . $table . '-default';
253  }
254 
255  if (($row['CType'] ?? '') === 'list' && ($row['list_type'] ?? '') !== '') {
256  $pluginIcon = $this->‪getIconForPlugin($row['list_type']);
257  if ($pluginIcon) {
258  $recordType[7] = $pluginIcon;
259  }
260  }
261 
262  krsort($recordType);
263  foreach ($recordType as $iconName) {
264  if ($this->iconRegistry->isRegistered($iconName)) {
265  return $iconName;
266  }
267  }
268 
269  return $this->iconRegistry->getDefaultIconIdentifier();
270  }
271 
278  protected function ‪getIconForPlugin(string $pluginName): ?string
279  {
280  $result = null;
281  $items = ‪$GLOBALS['TCA']['tt_content']['columns']['list_type']['config']['items'];
282  foreach ($items as $item) {
283  if ($item[1] === $pluginName) {
284  $result = $item[2];
285  break;
286  }
287  }
288 
289  return $result;
290  }
291 
300  protected function ‪getRecordTypeForPageType(string $typeName, string $suffix, string $table, string $fallbackTypeName = '1'): string
301  {
302  $recordType = $typeName . '-' . $suffix;
303 
304  // Check if typeicon class exists. If not fallback to page as typeName
305  if (!isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$recordType])) {
306  $recordType = $fallbackTypeName . '-' . $suffix;
307  }
308  return $recordType;
309  }
310 
320  protected function ‪mapRecordTypeToOverlayIdentifier($table, array $row)
321  {
322  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'] ?? [];
323  // Calculate for a given record the actual visibility at the moment
324  $status = [
325  'hidden' => false,
326  'starttime' => false,
327  'endtime' => false,
328  'futureendtime' => false,
329  'fe_group' => false,
330  'deleted' => false,
331  'protectedSection' => false,
332  'nav_hide' => !empty($row['nav_hide']),
333  ];
334  // Icon state based on "enableFields":
335  if (isset($tcaCtrl['enablecolumns']) && is_array($tcaCtrl['enablecolumns'])) {
336  $enableColumns = $tcaCtrl['enablecolumns'];
337  // If "hidden" is enabled:
338  if (isset($enableColumns['disabled']) && !empty($row[$enableColumns['disabled']])) {
339  $status['hidden'] = true;
340  }
341  // If a "starttime" is set and higher than current time:
342  if (!empty($enableColumns['starttime']) && ‪$GLOBALS['EXEC_TIME'] < (int)($row[$enableColumns['starttime']] ?? 0)) {
343  $status['starttime'] = true;
344  }
345  // If an "endtime" is set
346  if (!empty($enableColumns['endtime'])) {
347  if ((int)($row[$enableColumns['endtime']] ?? 0) > 0) {
348  if ((int)$row[$enableColumns['endtime']] < ‪$GLOBALS['EXEC_TIME']) {
349  // End-timing applies at this point.
350  $status['endtime'] = true;
351  } else {
352  // End-timing WILL apply in the future for this element.
353  $status['futureendtime'] = true;
354  }
355  }
356  }
357  // If a user-group field is set
358  if (!empty($enableColumns['fe_group']) && !empty($row[$enableColumns['fe_group']])) {
359  $status['fe_group'] = true;
360  }
361  }
362  // If "deleted" flag is set (only when listing records which are also deleted!)
363  if (isset($tcaCtrl['delete']) && !empty($row[$tcaCtrl['delete']])) {
364  $status['deleted'] = true;
365  }
366  // Detecting extendToSubpages (for pages only)
367  if ($table === 'pages' && (int)($row['extendToSubpages'] ?? 0) > 0) {
368  $status['protectedSection'] = true;
369  }
370  if (isset($row['t3ver_state'])
371  && ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
372  $status['deleted'] = true;
373  }
374 
375  // Now only show the status with the highest priority
376  $iconName = '';
377  foreach ($this->overlayPriorities as $priority) {
378  if ($status[$priority]) {
379  $iconName = $this->recordStatusMapping[$priority];
380  break;
381  }
382  }
383 
384  // Hook to define an alternative iconName
385  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['overrideIconOverlay'] ?? [] as $className) {
386  $hookObject = GeneralUtility::makeInstance($className);
387  if (method_exists($hookObject, 'postOverlayPriorityLookup')) {
388  $iconName = $hookObject->postOverlayPriorityLookup($table, $row, $status, $iconName);
389  }
390  }
391 
392  return $iconName;
393  }
394 
403  public function ‪getIconForFileExtension($fileExtension, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null)
404  {
405  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
406  return $this->‪getIcon($iconName, $size, $overlayIdentifier);
407  }
408 
427  public function ‪getIconForResource(
428  ResourceInterface $resource,
429  $size = ‪Icon::SIZE_DEFAULT,
430  $overlayIdentifier = null,
431  array $options = []
432  ) {
433  $iconIdentifier = null;
434 
435  // Folder
436  if ($resource instanceof FolderInterface) {
437  // non browsable storage
438  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
439  $iconIdentifier = 'apps-filetree-folder-locked';
440  } else {
441  // storage root
442  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
443  $iconIdentifier = 'apps-filetree-root';
444  }
445 
446  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
447 
448  // user/group mount root
449  if (!empty($options['mount-root'])) {
450  $iconIdentifier = 'apps-filetree-mount';
452  $overlayIdentifier = 'overlay-locked';
453  } elseif ($role === ‪FolderInterface::ROLE_USER_MOUNT) {
454  $overlayIdentifier = 'overlay-restricted';
455  }
456  }
457 
458  if ($iconIdentifier === null) {
459  // in folder tree view $options['folder-open'] can define an open folder icon
460  if (!empty($options['folder-open'])) {
461  $iconIdentifier = 'apps-filetree-folder-opened';
462  } else {
463  $iconIdentifier = 'apps-filetree-folder-default';
464  }
465 
466  if ($role === ‪FolderInterface::ROLE_TEMPORARY) {
467  $iconIdentifier = 'apps-filetree-folder-temp';
468  } elseif ($role === ‪FolderInterface::ROLE_RECYCLER) {
469  $iconIdentifier = 'apps-filetree-folder-recycler';
470  }
471  }
472 
473  // if locked add overlay
474  if ($resource instanceof InaccessibleFolder ||
475  !$resource->getStorage()->isBrowsable() ||
476  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
477  ) {
478  $overlayIdentifier = 'overlay-locked';
479  }
480  }
481  } elseif ($resource instanceof File) {
482  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
483 
484  // Check if we find an exact matching mime type
485  if ($mimeTypeIcon !== null) {
486  $iconIdentifier = $mimeTypeIcon;
487  } else {
488  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
489  if ($fileExtensionIcon !== 'mimetypes-other-other') {
490  // Fallback 1: icon by file extension
491  $iconIdentifier = $fileExtensionIcon;
492  } else {
493  // Fallback 2: icon by mime type with subtype replaced by *
494  $mimeTypeParts = explode('/', $resource->getMimeType());
495  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
496  if ($mimeTypeIcon !== null) {
497  $iconIdentifier = $mimeTypeIcon;
498  } else {
499  // Fallback 3: use 'mimetypes-other-other'
500  $iconIdentifier = $fileExtensionIcon;
501  }
502  }
503  }
504  if ($resource->isMissing()) {
505  $overlayIdentifier = 'overlay-missing';
506  }
507  }
508 
509  $event = $this->eventDispatcher->dispatch(
510  new ModifyIconForResourcePropertiesEvent(
511  $resource,
512  $size,
513  $options,
514  $iconIdentifier,
515  $overlayIdentifier
516  )
517  );
518  return $this->‪getIcon($event->getIconIdentifier(), $size, $event->getOverlayIdentifier());
519  }
520 
530  protected function ‪createIcon($identifier, $size, $overlayIdentifier = null, array $iconConfiguration = [])
531  {
532  $icon = GeneralUtility::makeInstance(Icon::class);
533  $icon->setIdentifier($identifier);
534  $icon->setSize($size);
535  $icon->setState($iconConfiguration['state'] ?: new IconState());
536  if (!empty($overlayIdentifier)) {
537  $icon->setOverlayIcon($this->‪getIcon($overlayIdentifier, ‪Icon::SIZE_OVERLAY));
538  }
539  if (!empty($iconConfiguration['options']['spinning'])) {
540  $icon->setSpinning(true);
541  }
542 
543  return $icon;
544  }
545 
549  public function ‪clearIconCache()
550  {
551  static::$iconCache = [];
552  }
553 }
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconRegistry
‪IconRegistry $iconRegistry
Definition: IconFactory.php:37
‪TYPO3\CMS\Core\Imaging
Definition: Dimension.php:16
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_DEFAULT
‪const SIZE_DEFAULT
Definition: Icon.php:35
‪TYPO3\CMS\Core\Imaging\IconFactory\clearIconCache
‪clearIconCache()
Definition: IconFactory.php:544
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Core\Imaging\IconFactory\$recordStatusMapping
‪string[] $recordStatusMapping
Definition: IconFactory.php:44
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Imaging\IconFactory\getRecordTypeForPageType
‪string getRecordTypeForPageType(string $typeName, string $suffix, string $table, string $fallbackTypeName='1')
Definition: IconFactory.php:295
‪TYPO3\CMS\Core\Resource\ResourceInterface\getIdentifier
‪string getIdentifier()
‪TYPO3\CMS\Core\Resource\ResourceInterface\getStorage
‪ResourceStorage getStorage()
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:61
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_OVERLAY
‪const SIZE_OVERLAY
Definition: Icon.php:46
‪TYPO3\CMS\Core\Resource\ResourceStorage\getRootLevelFolder
‪Folder getRootLevelFolder($respectFileMounts=true)
Definition: ResourceStorage.php:2654
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_TEMPORARY
‪const ROLE_TEMPORARY
Definition: FolderInterface.php:29
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForFileExtension
‪Icon getIconForFileExtension($fileExtension, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null)
Definition: IconFactory.php:398
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Core\Resource\InaccessibleFolder
Definition: InaccessibleFolder.php:29
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForResource
‪Icon getIconForResource(ResourceInterface $resource, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, array $options=[])
Definition: IconFactory.php:422
‪TYPO3\CMS\Core\Imaging\IconFactory\createIcon
‪Icon createIcon($identifier, $size, $overlayIdentifier=null, array $iconConfiguration=[])
Definition: IconFactory.php:525
‪TYPO3\CMS\Core\Resource\ResourceStorage\isBrowsable
‪bool isBrowsable()
Definition: ResourceStorage.php:413
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:24
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForRecord
‪Icon getIconForRecord($table, array $row, $size=Icon::SIZE_DEFAULT)
Definition: IconFactory.php:124
‪TYPO3\CMS\Core\Imaging\IconRegistry
Definition: IconRegistry.php:32
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconCache
‪static array $iconCache
Definition: IconFactory.php:57
‪TYPO3\CMS\Core\Imaging\IconFactory\$overlayPriorities
‪string[] $overlayPriorities
Definition: IconFactory.php:51
‪TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent
Definition: ModifyIconForResourcePropertiesEvent.php:27
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_USER_MOUNT
‪const ROLE_USER_MOUNT
Definition: FolderInterface.php:33
‪TYPO3\CMS\Core\Resource\ResourceStorage\checkFolderActionPermission
‪bool checkFolderActionPermission($action, ?Folder $folder=null)
Definition: ResourceStorage.php:782
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Core\Type\Icon\IconState
Definition: IconState.php:24
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_READONLY_MOUNT
‪const ROLE_READONLY_MOUNT
Definition: FolderInterface.php:32
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_RECYCLER
‪const ROLE_RECYCLER
Definition: FolderInterface.php:27
‪TYPO3\CMS\Core\Resource\FolderInterface
Definition: FolderInterface.php:22
‪TYPO3\CMS\Core\Resource\Folder\getIdentifier
‪string getIdentifier()
Definition: Folder.php:160
‪TYPO3\CMS\Core\Resource\ResourceInterface
Definition: ResourceInterface.php:22
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToIconIdentifier
‪string mapRecordTypeToIconIdentifier($table, array $row)
Definition: IconFactory.php:147
‪TYPO3\CMS\Core\Imaging\IconFactory\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: IconFactory.php:61
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForPlugin
‪string null getIconForPlugin(string $pluginName)
Definition: IconFactory.php:273
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToOverlayIdentifier
‪string mapRecordTypeToOverlayIdentifier($table, array $row)
Definition: IconFactory.php:315
‪TYPO3\CMS\Core\Imaging\IconFactory\__construct
‪__construct(EventDispatcherInterface $eventDispatcher, IconRegistry $iconRegistry, ContainerInterface $container)
Definition: IconFactory.php:70
‪TYPO3\CMS\Core\Imaging\IconFactory\getIcon
‪Icon getIcon($identifier, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, ?IconState $state=null)
Definition: IconFactory.php:86
‪TYPO3\CMS\Core\Imaging\IconFactory\$container
‪ContainerInterface $container
Definition: IconFactory.php:63