‪TYPO3CMS  ‪main
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 
70  public function ‪__construct(EventDispatcherInterface ‪$eventDispatcher, ‪IconRegistry ‪$iconRegistry, ContainerInterface ‪$container)
71  {
72  $this->eventDispatcher = ‪$eventDispatcher;
73  $this->iconRegistry = ‪$iconRegistry;
74  $this->container = ‪$container;
75  $this->recordStatusMapping = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['recordStatusMapping'];
76  $this->overlayPriorities = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['overlayPriorities'];
77  }
78 
87  public function ‪getIcon(‪$identifier, string|IconSize $size = IconSize::MEDIUM, $overlayIdentifier = null, \‪TYPO3\CMS\Core\Type\‪Icon\‪IconState|‪IconState $state = null)
88  {
89  if ($state instanceof ‪IconState) {
90  $stateValue = $state->value;
91  } else {
92  if ($state instanceof \‪TYPO3\CMS\Core\Type\Icon\‪IconState) {
93  trigger_error(
94  'Using the non-native enumeration TYPO3\CMS\Core\Type\Icon\IconState in IconFactory->getIcon()'
95  . ' will not work in TYPO3 v14.0 anymore. Use native TYPO3\CMS\Core\Imaging\IconState instead.',
96  E_USER_DEPRECATED
97  );
98  }
99  $stateValue = (string)$state;
100  }
101  if (is_string($size)) {
102  $size = IconSize::from($size);
103  $size->triggerDeprecation();
104  }
105  $cacheIdentifier = md5(‪$identifier . $size->value . $overlayIdentifier . $stateValue);
106  if (!empty(static::$iconCache[$cacheIdentifier])) {
107  return static::$iconCache[$cacheIdentifier];
108  }
109 
110  if (
111  !$this->iconRegistry->isDeprecated(‪$identifier)
112  && !$this->iconRegistry->isRegistered(‪$identifier)
113  ) {
114  // in case icon identifier is neither deprecated nor registered
115  ‪$identifier = $this->iconRegistry->getDefaultIconIdentifier();
116  }
117 
118  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier(‪$identifier);
119  $iconConfiguration['state'] = $stateValue;
120  $icon = $this->‪createIcon(‪$identifier, $size, $overlayIdentifier, $iconConfiguration);
121 
123  $iconProvider = $this->container->has($iconConfiguration['provider']) ?
124  $this->container->get($iconConfiguration['provider']) :
125  GeneralUtility::makeInstance($iconConfiguration['provider']);
126  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
127 
128  static::$iconCache[$cacheIdentifier] = $icon;
129 
130  return $icon;
131  }
132 
143  public function ‪getIconForRecord($table, array $row, $size = IconSize::MEDIUM)
144  {
145  if (is_string($size)) {
146  $size = IconSize::from($size);
147  $size->triggerDeprecation();
148  }
149  $iconIdentifier = $this->‪mapRecordTypeToIconIdentifier($table, $row);
150  $overlayIdentifier = $this->‪mapRecordTypeToOverlayIdentifier($table, $row);
151  return $this->‪getIcon($iconIdentifier, $size, $overlayIdentifier);
152  }
153 
170  public function ‪mapRecordTypeToIconIdentifier($table, array $row)
171  {
172  $recordType = [];
173  $ref = null;
174 
175  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
176  $column = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
177  if (isset($row[$column])) {
178  // even if not properly documented the value of the typeicon_column in a record could be
179  // an array (multiselect) in typeicon_classes a key could consist of a comma-separated string "foo,bar"
180  // but mostly it should be only one entry in that array
181  if (is_array($row[$column])) {
182  $recordType[1] = implode(',', $row[$column]);
183  } else {
184  $recordType[1] = $row[$column];
185  }
186  } else {
187  $recordType[1] = 'default';
188  }
189  // Workaround to give nav_hide pages a complete different icon
190  // Although it's not a separate doctype
191  // and to give root-pages an own icon
192  if ($table === 'pages') {
193  if (($row['nav_hide'] ?? 0) > 0) {
194  $recordType[2] = $this->‪getRecordTypeForPageType(
195  $recordType[1],
196  'hideinmenu',
197  $table
198  );
199  }
200  if (($row['is_siteroot'] ?? 0) > 0) {
201  $recordType[3] = $this->‪getRecordTypeForPageType(
202  $recordType[1],
203  'root',
204  $table
205  );
206  }
207  if (!empty($row['module'])) {
208  if (is_array($row['module'])) {
209  // field 'module' is configured as type 'select' in the TCA,
210  // so the value may have already been converted to an array
211  $moduleSuffix = reset($row['module']);
212  } else {
213  $moduleSuffix = $row['module'];
214  }
215  $recordType[4] = 'contains-' . $moduleSuffix;
216  }
217  if (($row['content_from_pid'] ?? 0) > 0) {
218  if ($row['is_siteroot'] ?? false) {
219  $recordType[4] = $this->‪getRecordTypeForPageType(
220  $recordType[1],
221  'contentFromPid-root',
222  $table
223  );
224  } else {
225  $suffix = (int)$row['nav_hide'] === 0 ? 'contentFromPid' : 'contentFromPid-hideinmenu';
226  $recordType[4] = $this->‪getRecordTypeForPageType($recordType[1], $suffix, $table, 'page');
227  }
228  }
229  }
230  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
231  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
232  ) {
233  foreach ($recordType as $key => $type) {
234  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
235  $recordType[$key] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
236  } else {
237  unset($recordType[$key]);
238  }
239  }
240  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'] ?? '';
241  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])
242  && isset($row[$column]) && is_string($row[$column])
243  ) {
244  $recordType[5] = str_replace(
245  '###TYPE###',
246  $row[$column] ?? '',
247  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']
248  );
249  }
250  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
251  $parameters = ['row' => $row];
252  $recordType[6] = GeneralUtility::callUserFunction(
253  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'],
254  $parameters,
255  $ref
256  );
257  }
258  } else {
259  foreach ($recordType as &$type) {
260  $type = 'tcarecords-' . $table . '-' . $type;
261  }
262  unset($type);
263  $recordType[0] = 'tcarecords-' . $table . '-default';
264  }
265  } elseif (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
266  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
267  ) {
268  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
269  } else {
270  $recordType[0] = 'tcarecords-' . $table . '-default';
271  }
272 
273  if (($row['CType'] ?? '') === 'list' && ($row['list_type'] ?? '') !== '') {
274  $pluginIcon = $this->‪getIconForPlugin($row['list_type']);
275  if ($pluginIcon) {
276  $recordType[7] = $pluginIcon;
277  }
278  }
279 
280  krsort($recordType);
281  foreach ($recordType as $iconName) {
282  if ($this->iconRegistry->isRegistered($iconName)) {
283  return $iconName;
284  }
285  }
286 
287  return $this->iconRegistry->getDefaultIconIdentifier();
288  }
289 
293  protected function ‪getIconForPlugin(string $pluginName): ?string
294  {
295  $result = null;
296  $items = ‪$GLOBALS['TCA']['tt_content']['columns']['list_type']['config']['items'];
297  foreach ($items as $item) {
298  if ($item['value'] === $pluginName) {
299  $result = $item['icon'];
300  break;
301  }
302  }
303 
304  return $result;
305  }
306 
311  protected function ‪getRecordTypeForPageType(string $typeName, string $suffix, string $table, string $fallbackTypeName = '1'): string
312  {
313  $recordType = $typeName . '-' . $suffix;
314 
315  // Check if typeicon class exists. If not fallback to page as typeName
316  if (!isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$recordType])) {
317  $recordType = $fallbackTypeName . '-' . $suffix;
318  }
319  return $recordType;
320  }
321 
331  protected function ‪mapRecordTypeToOverlayIdentifier($table, array $row)
332  {
333  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'] ?? [];
334  // Calculate for a given record the actual visibility at the moment
335  $status = [
336  'hidden' => false,
337  'starttime' => false,
338  'endtime' => false,
339  'futureendtime' => false,
340  'fe_group' => false,
341  'deleted' => false,
342  'protectedSection' => false,
343  'nav_hide' => !empty($row['nav_hide']),
344  ];
345  // Icon state based on "enableFields":
346  if (isset($tcaCtrl['enablecolumns']) && is_array($tcaCtrl['enablecolumns'])) {
347  $enableColumns = $tcaCtrl['enablecolumns'];
348  // If "hidden" is enabled:
349  if (isset($enableColumns['disabled']) && !empty($row[$enableColumns['disabled']])) {
350  $status['hidden'] = true;
351  }
352  // If a "starttime" is set and higher than current time:
353  if (!empty($enableColumns['starttime']) && ‪$GLOBALS['EXEC_TIME'] < (int)($row[$enableColumns['starttime']] ?? 0)) {
354  $status['starttime'] = true;
355  }
356  // If an "endtime" is set
357  if (!empty($enableColumns['endtime'])) {
358  if ((int)($row[$enableColumns['endtime']] ?? 0) > 0) {
359  if ((int)$row[$enableColumns['endtime']] < ‪$GLOBALS['EXEC_TIME']) {
360  // End-timing applies at this point.
361  $status['endtime'] = true;
362  } else {
363  // End-timing WILL apply in the future for this element.
364  $status['futureendtime'] = true;
365  }
366  }
367  }
368  // If a user-group field is set
369  if (!empty($enableColumns['fe_group']) && !empty($row[$enableColumns['fe_group']])) {
370  $status['fe_group'] = true;
371  }
372  }
373  // If "deleted" flag is set (only when listing records which are also deleted!)
374  if (isset($tcaCtrl['delete']) && !empty($row[$tcaCtrl['delete']])) {
375  $status['deleted'] = true;
376  }
377  // Detecting extendToSubpages (for pages only)
378  if ($table === 'pages' && (int)($row['extendToSubpages'] ?? 0) > 0) {
379  $status['protectedSection'] = true;
380  }
381  if (VersionState::tryFrom($row['t3ver_state'] ?? 0) === VersionState::DELETE_PLACEHOLDER) {
382  $status['deleted'] = true;
383  }
384 
385  // Now only show the status with the highest priority
386  $iconName = '';
387  foreach ($this->overlayPriorities as $priority) {
388  if ($status[$priority]) {
389  $iconName = $this->recordStatusMapping[$priority];
390  break;
391  }
392  }
393 
394  return $this->eventDispatcher->dispatch(
395  new ModifyRecordOverlayIconIdentifierEvent($iconName, $table, $row, $status)
396  )->getOverlayIconIdentifier();
397  }
398 
409  public function ‪getIconForFileExtension($fileExtension, string|IconSize $size = IconSize::MEDIUM, $overlayIdentifier = null)
410  {
411  if (is_string($size)) {
412  $size = IconSize::from($size);
413  $size->triggerDeprecation();
414  }
415  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
416  return $this->‪getIcon($iconName, $size, $overlayIdentifier);
417  }
418 
439  public function ‪getIconForResource(
440  ResourceInterface $resource,
441  string|IconSize $size = IconSize::MEDIUM,
442  $overlayIdentifier = null,
443  array $options = []
444  ) {
445  $iconIdentifier = null;
446 
447  // Folder
448  if ($resource instanceof FolderInterface) {
449  // non browsable storage
450  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
451  $iconIdentifier = 'apps-filetree-folder-locked';
452  } else {
453  // storage root
454  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
455  $iconIdentifier = 'apps-filetree-root';
456  }
457 
458  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
459 
460  // user/group mount root
461  if (!empty($options['mount-root'])) {
462  $iconIdentifier = 'apps-filetree-mount';
464  $overlayIdentifier = 'overlay-locked';
465  } elseif ($role === ‪FolderInterface::ROLE_USER_MOUNT) {
466  $overlayIdentifier = 'overlay-restricted';
467  }
468  }
469 
470  if ($iconIdentifier === null) {
471  // in folder tree view $options['folder-open'] can define an open folder icon
472  if (!empty($options['folder-open'])) {
473  $iconIdentifier = 'apps-filetree-folder-opened';
474  } else {
475  $iconIdentifier = 'apps-filetree-folder-default';
476  }
477 
478  if ($role === ‪FolderInterface::ROLE_TEMPORARY) {
479  $iconIdentifier = 'apps-filetree-folder-temp';
480  } elseif ($role === ‪FolderInterface::ROLE_RECYCLER) {
481  $iconIdentifier = 'apps-filetree-folder-recycler';
482  }
483  }
484 
485  // if locked add overlay
486  if ($resource instanceof InaccessibleFolder ||
487  !$resource->getStorage()->isBrowsable() ||
488  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
489  ) {
490  $overlayIdentifier = 'overlay-locked';
491  }
492  }
493  } elseif ($resource instanceof File) {
494  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
495 
496  // Check if we find an exact matching mime type
497  if ($mimeTypeIcon !== null) {
498  $iconIdentifier = $mimeTypeIcon;
499  } else {
500  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
501  if ($fileExtensionIcon !== 'mimetypes-other-other') {
502  // Fallback 1: icon by file extension
503  $iconIdentifier = $fileExtensionIcon;
504  } else {
505  // Fallback 2: icon by mime type with subtype replaced by *
506  $mimeTypeParts = explode('/', $resource->getMimeType());
507  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
508  if ($mimeTypeIcon !== null) {
509  $iconIdentifier = $mimeTypeIcon;
510  } else {
511  // Fallback 3: use 'mimetypes-other-other'
512  $iconIdentifier = $fileExtensionIcon;
513  }
514  }
515  }
516  if ($resource->isMissing()) {
517  $overlayIdentifier = 'overlay-missing';
518  }
519  }
520 
521  if (is_string($size)) {
522  $size = IconSize::from($size);
523  $size->triggerDeprecation();
524  }
525 
526  $event = $this->eventDispatcher->dispatch(
527  new ModifyIconForResourcePropertiesEvent(
528  $resource,
529  $size,
530  $options,
531  $iconIdentifier,
532  $overlayIdentifier
533  )
534  );
535  return $this->‪getIcon($event->getIconIdentifier(), $size, $event->getOverlayIdentifier());
536  }
537 
547  protected function ‪createIcon(‪$identifier, IconSize $size, $overlayIdentifier = null, array $iconConfiguration = [])
548  {
549  $icon = GeneralUtility::makeInstance(Icon::class);
550  $icon->setIdentifier(‪$identifier);
551  $icon->setSize($size);
552  $iconState = IconState::tryFrom($iconConfiguration['state']) ?? IconState::STATE_DEFAULT;
553  $icon->setState($iconState);
554  if (!empty($overlayIdentifier)) {
555  $icon->setOverlayIcon($this->‪getIcon($overlayIdentifier, IconSize::OVERLAY));
556  }
557  if (!empty($iconConfiguration['options']['spinning'])) {
558  $icon->setSpinning(true);
559  }
560 
561  return $icon;
562  }
563 
567  public function ‪clearIconCache()
568  {
569  static::$iconCache = [];
570  }
571 }
‪TYPO3\CMS\Core\Resource\ResourceInterface\getIdentifier
‪getIdentifier()
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconRegistry
‪IconRegistry $iconRegistry
Definition: IconFactory.php:37
‪TYPO3\CMS\Core\Imaging
Definition: Dimension.php:16
‪TYPO3\CMS\Core\Imaging\IconFactory\getRecordTypeForPageType
‪getRecordTypeForPageType(string $typeName, string $suffix, string $table, string $fallbackTypeName='1')
Definition: IconFactory.php:306
‪TYPO3\CMS\Core\Imaging\IconFactory\getIcon
‪Icon getIcon($identifier, string|IconSize $size=IconSize::MEDIUM, $overlayIdentifier=null, \TYPO3\CMS\Core\Type\Icon\IconState|IconState $state=null)
Definition: IconFactory.php:82
‪TYPO3\CMS\Core\Imaging\IconFactory\clearIconCache
‪clearIconCache()
Definition: IconFactory.php:562
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForResource
‪Icon getIconForResource(ResourceInterface $resource, string|IconSize $size=IconSize::MEDIUM, $overlayIdentifier=null, array $options=[])
Definition: IconFactory.php:434
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:27
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3
‪TYPO3\CMS\Core\Resource\ResourceInterface\getStorage
‪getStorage()
‪TYPO3\CMS\Core\Imaging\IconFactory\createIcon
‪Icon createIcon($identifier, IconSize $size, $overlayIdentifier=null, array $iconConfiguration=[])
Definition: IconFactory.php:542
‪TYPO3\CMS\Core\Imaging\Event\ModifyRecordOverlayIconIdentifierEvent
Definition: ModifyRecordOverlayIconIdentifierEvent.php:24
‪TYPO3\CMS\Core\Imaging\IconFactory\$recordStatusMapping
‪string[] $recordStatusMapping
Definition: IconFactory.php:44
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForFileExtension
‪Icon getIconForFileExtension($fileExtension, string|IconSize $size=IconSize::MEDIUM, $overlayIdentifier=null)
Definition: IconFactory.php:404
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_TEMPORARY
‪const ROLE_TEMPORARY
Definition: FolderInterface.php:31
‪TYPO3\CMS\Core\Resource\InaccessibleFolder
Definition: InaccessibleFolder.php:29
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForRecord
‪Icon getIconForRecord($table, array $row, $size=IconSize::MEDIUM)
Definition: IconFactory.php:138
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪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:28
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_USER_MOUNT
‪const ROLE_USER_MOUNT
Definition: FolderInterface.php:35
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_READONLY_MOUNT
‪const ROLE_READONLY_MOUNT
Definition: FolderInterface.php:34
‪$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:29
‪TYPO3\CMS\Core\Resource\FolderInterface
Definition: FolderInterface.php:24
‪TYPO3\CMS\Core\Imaging\IconState
‪IconState
Definition: IconState.php:24
‪TYPO3\CMS\Core\Resource\ResourceInterface
Definition: ResourceInterface.php:21
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForPlugin
‪getIconForPlugin(string $pluginName)
Definition: IconFactory.php:288
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToIconIdentifier
‪string mapRecordTypeToIconIdentifier($table, array $row)
Definition: IconFactory.php:165
‪TYPO3\CMS\Core\Imaging\IconFactory\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: IconFactory.php:61
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToOverlayIdentifier
‪string mapRecordTypeToOverlayIdentifier($table, array $row)
Definition: IconFactory.php:326
‪TYPO3\CMS\Core\Imaging\IconFactory\__construct
‪__construct(EventDispatcherInterface $eventDispatcher, IconRegistry $iconRegistry, ContainerInterface $container)
Definition: IconFactory.php:65
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Imaging\IconFactory\$container
‪ContainerInterface $container
Definition: IconFactory.php:63