‪TYPO3CMS  10.4
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\EventDispatcher\EventDispatcherInterface;
27 
33 {
37  protected ‪$iconRegistry;
38 
45  protected ‪$recordStatusMapping = [];
46 
53  protected ‪$overlayPriorities = [];
54 
60  protected static ‪$iconCache = [];
61 
65  protected ‪$eventDispatcher;
66 
71  public function ‪__construct(EventDispatcherInterface ‪$eventDispatcher, ‪IconRegistry ‪$iconRegistry)
72  {
73  $this->eventDispatcher = ‪$eventDispatcher;
74  $this->iconRegistry = ‪$iconRegistry;
75  $this->recordStatusMapping = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['recordStatusMapping'];
76  $this->overlayPriorities = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['overlayPriorities'];
77  }
78 
86  public function ‪getIcon($identifier, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null, ‪IconState $state = null)
87  {
88  $cacheIdentifier = md5($identifier . $size . $overlayIdentifier . (string)$state);
89  if (!empty(static::$iconCache[$cacheIdentifier])) {
90  return static::$iconCache[$cacheIdentifier];
91  }
92 
93  if (
94  !$this->iconRegistry->isDeprecated($identifier)
95  && !$this->iconRegistry->isRegistered($identifier)
96  ) {
97  // in case icon identifier is neither deprecated nor registered
98  $identifier = $this->iconRegistry->getDefaultIconIdentifier();
99  }
100 
101  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
102  $iconConfiguration['state'] = $state;
103  $icon = $this->‪createIcon($identifier, $size, $overlayIdentifier, $iconConfiguration);
104 
106  $iconProvider = GeneralUtility::makeInstance($iconConfiguration['provider']);
107  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
108 
109  static::$iconCache[$cacheIdentifier] = $icon;
110 
111  return $icon;
112  }
113 
122  public function ‪getIconForRecord($table, array $row, $size = ‪Icon::SIZE_DEFAULT)
123  {
124  $iconIdentifier = $this->‪mapRecordTypeToIconIdentifier($table, $row);
125  $overlayIdentifier = $this->‪mapRecordTypeToOverlayIdentifier($table, $row);
126  return $this->‪getIcon($iconIdentifier, $size, $overlayIdentifier);
127  }
128 
145  public function ‪mapRecordTypeToIconIdentifier($table, array $row)
146  {
147  $recordType = [];
148  $ref = null;
149 
150  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
151  $column = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
152  if (isset($row[$column])) {
153  // even if not properly documented the value of the typeicon_column in a record could be
154  // an array (multiselect) in typeicon_classes a key could consist of a comma-separated string "foo,bar"
155  // but mostly it should be only one entry in that array
156  if (is_array($row[$column])) {
157  $recordType[1] = implode(',', $row[$column]);
158  } else {
159  $recordType[1] = $row[$column];
160  }
161  } else {
162  $recordType[1] = 'default';
163  }
164  // Workaround to give nav_hide pages a complete different icon
165  // Although it's not a separate doctype
166  // and to give root-pages an own icon
167  if ($table === 'pages') {
168  if ((int)$row['nav_hide'] > 0) {
169  $recordType[2] = $this->‪getRecordTypeForPageType(
170  $recordType[1],
171  'hideinmenu',
172  $table
173  );
174  }
175  if ((int)$row['is_siteroot'] > 0) {
176  $recordType[3] = $this->‪getRecordTypeForPageType(
177  $recordType[1],
178  'root',
179  $table
180  );
181  }
182  if (!empty($row['module'])) {
183  $recordType[4] = 'contains-' . $row['module'];
184  }
185  if ((int)$row['content_from_pid'] > 0) {
186  if ($row['is_siteroot']) {
187  $recordType[4] = $this->‪getRecordTypeForPageType(
188  $recordType[1],
189  'contentFromPid-root',
190  $table
191  );
192  } else {
193  $suffix = (int)$row['nav_hide'] === 0 ? 'contentFromPid' : 'contentFromPid-hideinmenu';
194  $recordType[4] = $this->‪getRecordTypeForPageType($recordType[1], $suffix, $table);
195  }
196  }
197  }
198  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
199  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
200  ) {
201  foreach ($recordType as $key => $type) {
202  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
203  $recordType[$key] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
204  } else {
205  unset($recordType[$key]);
206  }
207  }
208  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
209  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])) {
210  $recordType[5] = str_replace(
211  '###TYPE###',
212  $row[$column],
213  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']
214  );
215  }
216  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
217  $parameters = ['row' => $row];
218  $recordType[6] = GeneralUtility::callUserFunction(
219  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'],
220  $parameters,
221  $ref
222  );
223  }
224  } else {
225  foreach ($recordType as &$type) {
226  $type = 'tcarecords-' . $table . '-' . $type;
227  }
228  unset($type);
229  $recordType[0] = 'tcarecords-' . $table . '-default';
230  }
231  } elseif (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
232  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
233  ) {
234  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
235  } else {
236  $recordType[0] = 'tcarecords-' . $table . '-default';
237  }
238 
239  krsort($recordType);
240  foreach ($recordType as $iconName) {
241  if ($this->iconRegistry->isRegistered($iconName)) {
242  return $iconName;
243  }
244  }
245 
246  return $this->iconRegistry->getDefaultIconIdentifier();
247  }
248 
257  protected function ‪getRecordTypeForPageType(string $typeName, string $suffix, string $table): string
258  {
259  $recordType = $typeName . '-' . $suffix;
260 
261  // Check if typeicon class exists. If not fallback to page as typeName
262  if (!isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$recordType])) {
263  $recordType = 'page-' . $suffix;
264  }
265  return $recordType;
266  }
267 
277  protected function ‪mapRecordTypeToOverlayIdentifier($table, array $row)
278  {
279  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'];
280  // Calculate for a given record the actual visibility at the moment
281  $status = [
282  'hidden' => false,
283  'starttime' => false,
284  'endtime' => false,
285  'futureendtime' => false,
286  'fe_group' => false,
287  'deleted' => false,
288  'protectedSection' => false,
289  'nav_hide' => !empty($row['nav_hide']),
290  ];
291  // Icon state based on "enableFields":
292  if (isset($tcaCtrl['enablecolumns']) && is_array($tcaCtrl['enablecolumns'])) {
293  $enableColumns = $tcaCtrl['enablecolumns'];
294  // If "hidden" is enabled:
295  if (isset($enableColumns['disabled']) && !empty($row[$enableColumns['disabled']])) {
296  $status['hidden'] = true;
297  }
298  // If a "starttime" is set and higher than current time:
299  if (!empty($enableColumns['starttime']) && ‪$GLOBALS['EXEC_TIME'] < (int)$row[$enableColumns['starttime']]) {
300  $status['starttime'] = true;
301  }
302  // If an "endtime" is set
303  if (!empty($enableColumns['endtime'])) {
304  if ((int)$row[$enableColumns['endtime']] > 0) {
305  if ((int)$row[$enableColumns['endtime']] < ‪$GLOBALS['EXEC_TIME']) {
306  // End-timing applies at this point.
307  $status['endtime'] = true;
308  } else {
309  // End-timing WILL apply in the future for this element.
310  $status['futureendtime'] = true;
311  }
312  }
313  }
314  // If a user-group field is set
315  if (!empty($enableColumns['fe_group']) && $row[$enableColumns['fe_group']]) {
316  $status['fe_group'] = true;
317  }
318  }
319  // If "deleted" flag is set (only when listing records which are also deleted!)
320  if (isset($tcaCtrl['delete']) && !empty($row[$tcaCtrl['delete']])) {
321  $status['deleted'] = true;
322  }
323  // Detecting extendToSubpages (for pages only)
324  if ($table === 'pages' && (int)$row['extendToSubpages'] > 0) {
325  $status['protectedSection'] = true;
326  }
327  if (isset($row['t3ver_state'])
328  && ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
329  $status['deleted'] = true;
330  }
331 
332  // Now only show the status with the highest priority
333  $iconName = '';
334  foreach ($this->overlayPriorities as $priority) {
335  if ($status[$priority]) {
336  $iconName = $this->recordStatusMapping[$priority];
337  break;
338  }
339  }
340 
341  // Hook to define an alternative iconName
342  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['overrideIconOverlay'] ?? [] as $className) {
343  $hookObject = GeneralUtility::makeInstance($className);
344  if (method_exists($hookObject, 'postOverlayPriorityLookup')) {
345  $iconName = $hookObject->postOverlayPriorityLookup($table, $row, $status, $iconName);
346  }
347  }
348 
349  return $iconName;
350  }
351 
360  public function ‪getIconForFileExtension($fileExtension, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null)
361  {
362  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
363  return $this->‪getIcon($iconName, $size, $overlayIdentifier);
364  }
365 
384  public function ‪getIconForResource(
385  ResourceInterface $resource,
386  $size = ‪Icon::SIZE_DEFAULT,
387  $overlayIdentifier = null,
388  array $options = []
389  ) {
390  $iconIdentifier = null;
391 
392  // Folder
393  if ($resource instanceof FolderInterface) {
394  // non browsable storage
395  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
396  $iconIdentifier = 'apps-filetree-folder-locked';
397  } else {
398  // storage root
399  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
400  $iconIdentifier = 'apps-filetree-root';
401  }
402 
403  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
404 
405  // user/group mount root
406  if (!empty($options['mount-root'])) {
407  $iconIdentifier = 'apps-filetree-mount';
409  $overlayIdentifier = 'overlay-locked';
410  } elseif ($role === ‪FolderInterface::ROLE_USER_MOUNT) {
411  $overlayIdentifier = 'overlay-restricted';
412  }
413  }
414 
415  if ($iconIdentifier === null) {
416  // in folder tree view $options['folder-open'] can define an open folder icon
417  if (!empty($options['folder-open'])) {
418  $iconIdentifier = 'apps-filetree-folder-opened';
419  } else {
420  $iconIdentifier = 'apps-filetree-folder-default';
421  }
422 
423  if ($role === ‪FolderInterface::ROLE_TEMPORARY) {
424  $iconIdentifier = 'apps-filetree-folder-temp';
425  } elseif ($role === ‪FolderInterface::ROLE_RECYCLER) {
426  $iconIdentifier = 'apps-filetree-folder-recycler';
427  }
428  }
429 
430  // if locked add overlay
431  if ($resource instanceof InaccessibleFolder ||
432  !$resource->getStorage()->isBrowsable() ||
433  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
434  ) {
435  $overlayIdentifier = 'overlay-locked';
436  }
437  }
438  } elseif ($resource instanceof File) {
439  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
440 
441  // Check if we find an exact matching mime type
442  if ($mimeTypeIcon !== null) {
443  $iconIdentifier = $mimeTypeIcon;
444  } else {
445  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
446  if ($fileExtensionIcon !== 'mimetypes-other-other') {
447  // Fallback 1: icon by file extension
448  $iconIdentifier = $fileExtensionIcon;
449  } else {
450  // Fallback 2: icon by mime type with subtype replaced by *
451  $mimeTypeParts = explode('/', $resource->getMimeType());
452  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
453  if ($mimeTypeIcon !== null) {
454  $iconIdentifier = $mimeTypeIcon;
455  } else {
456  // Fallback 3: use 'mimetypes-other-other'
457  $iconIdentifier = $fileExtensionIcon;
458  }
459  }
460  }
461  if ($resource->isMissing()) {
462  $overlayIdentifier = 'overlay-missing';
463  }
464  }
465 
466  $event = $this->eventDispatcher->dispatch(
467  new ModifyIconForResourcePropertiesEvent(
468  $resource,
469  $size,
470  $options,
471  $iconIdentifier,
472  $overlayIdentifier
473  )
474  );
475  return $this->‪getIcon($event->getIconIdentifier(), $size, $event->getOverlayIdentifier());
476  }
477 
487  protected function ‪createIcon($identifier, $size, $overlayIdentifier = null, array $iconConfiguration = [])
488  {
489  $icon = GeneralUtility::makeInstance(Icon::class);
490  $icon->setIdentifier($identifier);
491  $icon->setSize($size);
492  $icon->setState($iconConfiguration['state'] ?: new IconState());
493  if (!empty($overlayIdentifier)) {
494  $icon->setOverlayIcon($this->‪getIcon($overlayIdentifier, ‪Icon::SIZE_OVERLAY));
495  }
496  if (!empty($iconConfiguration['options']['spinning'])) {
497  $icon->setSpinning(true);
498  }
499 
500  return $icon;
501  }
502 
506  public function ‪clearIconCache()
507  {
508  static::$iconCache = [];
509  }
510 }
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconRegistry
‪IconRegistry $iconRegistry
Definition: IconFactory.php:36
‪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:501
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Core\Imaging\IconFactory\$recordStatusMapping
‪string[] $recordStatusMapping
Definition: IconFactory.php:43
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:33
‪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:55
‪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:2615
‪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:355
‪TYPO3\CMS\Core\Imaging\IconFactory\getRecordTypeForPageType
‪string getRecordTypeForPageType(string $typeName, string $suffix, string $table)
Definition: IconFactory.php:252
‪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\Resource\ResourceStorage\checkFolderActionPermission
‪bool checkFolderActionPermission($action, Folder $folder=null)
Definition: ResourceStorage.php:760
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForResource
‪Icon getIconForResource(ResourceInterface $resource, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, array $options=[])
Definition: IconFactory.php:379
‪TYPO3\CMS\Core\Imaging\IconFactory\createIcon
‪Icon createIcon($identifier, $size, $overlayIdentifier=null, array $iconConfiguration=[])
Definition: IconFactory.php:482
‪TYPO3\CMS\Core\Resource\ResourceStorage\isBrowsable
‪bool isBrowsable()
Definition: ResourceStorage.php:393
‪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:117
‪TYPO3\CMS\Core\Imaging\IconRegistry
Definition: IconRegistry.php:38
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconCache
‪static array $iconCache
Definition: IconFactory.php:56
‪TYPO3\CMS\Core\Imaging\IconFactory\$overlayPriorities
‪string[] $overlayPriorities
Definition: IconFactory.php:50
‪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\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:5
‪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:140
‪TYPO3\CMS\Core\Imaging\IconFactory\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: IconFactory.php:60
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Imaging\IconFactory\__construct
‪__construct(EventDispatcherInterface $eventDispatcher, IconRegistry $iconRegistry)
Definition: IconFactory.php:66
‪TYPO3\CMS\Core\Imaging\IconFactory\getIcon
‪Icon getIcon($identifier, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, IconState $state=null)
Definition: IconFactory.php:81
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToOverlayIdentifier
‪string mapRecordTypeToOverlayIdentifier($table, array $row)
Definition: IconFactory.php:272