‪TYPO3CMS  9.5
IconFactory.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
25 
31 {
35  protected ‪$iconRegistry;
36 
43  protected ‪$recordStatusMapping = [];
44 
51  protected ‪$overlayPriorities = [];
52 
58  protected static ‪$iconCache = [];
59 
63  public function ‪__construct(‪IconRegistry ‪$iconRegistry = null)
64  {
65  $this->iconRegistry = ‪$iconRegistry ? ‪$iconRegistry : GeneralUtility::makeInstance(IconRegistry::class);
66  $this->recordStatusMapping = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['recordStatusMapping'];
67  $this->overlayPriorities = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['overlayPriorities'];
68  }
69 
77  public function ‪getIcon($identifier, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null, ‪IconState $state = null)
78  {
79  $cacheIdentifier = md5($identifier . $size . $overlayIdentifier . (string)$state);
80  if (!empty(static::$iconCache[$cacheIdentifier])) {
81  return static::$iconCache[$cacheIdentifier];
82  }
83 
84  if (
85  !$this->iconRegistry->isDeprecated($identifier)
86  && !$this->iconRegistry->isRegistered($identifier)
87  ) {
88  // in case icon identifier is neither deprecated nor registered
89  $identifier = $this->iconRegistry->getDefaultIconIdentifier();
90  }
91 
92  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
93  $iconConfiguration['state'] = $state;
94  $icon = $this->‪createIcon($identifier, $size, $overlayIdentifier, $iconConfiguration);
95 
97  $iconProvider = GeneralUtility::makeInstance($iconConfiguration['provider']);
98  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
99 
100  static::$iconCache[$cacheIdentifier] = $icon;
101 
102  return $icon;
103  }
104 
113  public function ‪getIconForRecord($table, array $row, $size = ‪Icon::SIZE_DEFAULT)
114  {
115  $iconIdentifier = $this->‪mapRecordTypeToIconIdentifier($table, $row);
116  $overlayIdentifier = $this->‪mapRecordTypeToOverlayIdentifier($table, $row);
117  return $this->‪getIcon($iconIdentifier, $size, $overlayIdentifier);
118  }
119 
136  public function ‪mapRecordTypeToIconIdentifier($table, array $row)
137  {
138  $recordType = [];
139  $ref = null;
140 
141  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
142  $column = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
143  if (isset($row[$column])) {
144  // even if not properly documented the value of the typeicon_column in a record could be
145  // an array (multiselect) in typeicon_classes a key could consist of a comma-separated string "foo,bar"
146  // but mostly it should be only one entry in that array
147  if (is_array($row[$column])) {
148  $recordType[1] = implode(',', $row[$column]);
149  } else {
150  $recordType[1] = $row[$column];
151  }
152  } else {
153  $recordType[1] = 'default';
154  }
155  // Workaround to give nav_hide pages a complete different icon
156  // Although it's not a separate doctype
157  // and to give root-pages an own icon
158  if ($table === 'pages') {
159  if ((int)$row['nav_hide'] > 0) {
160  $recordType[2] = $recordType[1] . '-hideinmenu';
161  }
162  if ((int)$row['is_siteroot'] > 0) {
163  $recordType[3] = $recordType[1] . '-root';
164  }
165  if (!empty($row['module'])) {
166  $recordType[4] = 'contains-' . $row['module'];
167  }
168  if ((int)$row['content_from_pid'] > 0) {
169  if ($row['is_siteroot']) {
170  $recordType[4] = 'page-contentFromPid-root';
171  } else {
172  $recordType[4] = (int)$row['nav_hide'] === 0
173  ? 'page-contentFromPid' : 'page-contentFromPid-hideinmenu';
174  }
175  }
176  }
177  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
178  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
179  ) {
180  foreach ($recordType as $key => $type) {
181  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
182  $recordType[$key] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
183  } else {
184  unset($recordType[$key]);
185  }
186  }
187  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
188  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])) {
189  $recordType[5] = str_replace(
190  '###TYPE###',
191  $row[$column],
192  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']
193  );
194  }
195  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
196  $parameters = ['row' => $row];
197  $recordType[6] = GeneralUtility::callUserFunction(
198  ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'],
199  $parameters,
200  $ref
201  );
202  }
203  } else {
204  foreach ($recordType as &$type) {
205  $type = 'tcarecords-' . $table . '-' . $type;
206  }
207  unset($type);
208  $recordType[0] = 'tcarecords-' . $table . '-default';
209  }
210  } elseif (isset(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
211  && is_array(‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
212  ) {
213  $recordType[0] = ‪$GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
214  } else {
215  $recordType[0] = 'tcarecords-' . $table . '-default';
216  }
217 
218  krsort($recordType);
219  foreach ($recordType as $iconName) {
220  if ($this->iconRegistry->isRegistered($iconName)) {
221  return $iconName;
222  }
223  }
224 
225  return $this->iconRegistry->getDefaultIconIdentifier();
226  }
227 
237  protected function ‪mapRecordTypeToOverlayIdentifier($table, array $row)
238  {
239  $tcaCtrl = ‪$GLOBALS['TCA'][$table]['ctrl'];
240  // Calculate for a given record the actual visibility at the moment
241  $status = [
242  'hidden' => false,
243  'starttime' => false,
244  'endtime' => false,
245  'futureendtime' => false,
246  'fe_group' => false,
247  'deleted' => false,
248  'protectedSection' => false,
249  'nav_hide' => !empty($row['nav_hide']),
250  ];
251  // Icon state based on "enableFields":
252  if (isset($tcaCtrl['enablecolumns']) && is_array($tcaCtrl['enablecolumns'])) {
253  $enableColumns = $tcaCtrl['enablecolumns'];
254  // If "hidden" is enabled:
255  if (isset($enableColumns['disabled']) && !empty($row[$enableColumns['disabled']])) {
256  $status['hidden'] = true;
257  }
258  // If a "starttime" is set and higher than current time:
259  if (!empty($enableColumns['starttime']) && ‪$GLOBALS['EXEC_TIME'] < (int)$row[$enableColumns['starttime']]) {
260  $status['starttime'] = true;
261  }
262  // If an "endtime" is set
263  if (!empty($enableColumns['endtime'])) {
264  if ((int)$row[$enableColumns['endtime']] > 0) {
265  if ((int)$row[$enableColumns['endtime']] < ‪$GLOBALS['EXEC_TIME']) {
266  // End-timing applies at this point.
267  $status['endtime'] = true;
268  } else {
269  // End-timing WILL apply in the future for this element.
270  $status['futureendtime'] = true;
271  }
272  }
273  }
274  // If a user-group field is set
275  if (!empty($enableColumns['fe_group']) && $row[$enableColumns['fe_group']]) {
276  $status['fe_group'] = true;
277  }
278  }
279  // If "deleted" flag is set (only when listing records which are also deleted!)
280  if (isset($tcaCtrl['delete']) && !empty($row[$tcaCtrl['delete']])) {
281  $status['deleted'] = true;
282  }
283  // Detecting extendToSubpages (for pages only)
284  if ($table === 'pages' && (int)$row['extendToSubpages'] > 0) {
285  $status['protectedSection'] = true;
286  }
287  if (isset($row['t3ver_state'])
288  && ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
289  $status['deleted'] = true;
290  }
291 
292  // Now only show the status with the highest priority
293  $iconName = '';
294  foreach ($this->overlayPriorities as $priority) {
295  if ($status[$priority]) {
296  $iconName = $this->recordStatusMapping[$priority];
297  break;
298  }
299  }
300 
301  // Hook to define an alternative iconName
302  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['overrideIconOverlay'] ?? [] as $className) {
303  $hookObject = GeneralUtility::makeInstance($className);
304  if (method_exists($hookObject, 'postOverlayPriorityLookup')) {
305  $iconName = $hookObject->postOverlayPriorityLookup($table, $row, $status, $iconName);
306  }
307  }
308 
309  return $iconName;
310  }
311 
320  public function ‪getIconForFileExtension($fileExtension, $size = ‪Icon::SIZE_DEFAULT, $overlayIdentifier = null)
321  {
322  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
323  return $this->‪getIcon($iconName, $size, $overlayIdentifier);
324  }
325 
344  public function ‪getIconForResource(
345  ResourceInterface $resource,
346  $size = ‪Icon::SIZE_DEFAULT,
347  $overlayIdentifier = null,
348  array $options = []
349  ) {
350  $iconIdentifier = null;
351 
352  // Folder
353  if ($resource instanceof FolderInterface) {
354  // non browsable storage
355  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
356  $iconIdentifier = 'apps-filetree-folder-locked';
357  } else {
358  // storage root
359  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
360  $iconIdentifier = 'apps-filetree-root';
361  }
362 
363  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
364 
365  // user/group mount root
366  if (!empty($options['mount-root'])) {
367  $iconIdentifier = 'apps-filetree-mount';
369  $overlayIdentifier = 'overlay-locked';
370  } elseif ($role === ‪FolderInterface::ROLE_USER_MOUNT) {
371  $overlayIdentifier = 'overlay-restricted';
372  }
373  }
374 
375  if ($iconIdentifier === null) {
376  // in folder tree view $options['folder-open'] can define an open folder icon
377  if (!empty($options['folder-open'])) {
378  $iconIdentifier = 'apps-filetree-folder-opened';
379  } else {
380  $iconIdentifier = 'apps-filetree-folder-default';
381  }
382 
383  if ($role === ‪FolderInterface::ROLE_TEMPORARY) {
384  $iconIdentifier = 'apps-filetree-folder-temp';
385  } elseif ($role === ‪FolderInterface::ROLE_RECYCLER) {
386  $iconIdentifier = 'apps-filetree-folder-recycler';
387  }
388  }
389 
390  // if locked add overlay
391  if ($resource instanceof InaccessibleFolder ||
392  !$resource->getStorage()->isBrowsable() ||
393  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
394  ) {
395  $overlayIdentifier = 'overlay-locked';
396  }
397  }
398  } elseif ($resource instanceof File) {
399  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
400 
401  // Check if we find a exact matching mime type
402  if ($mimeTypeIcon !== null) {
403  $iconIdentifier = $mimeTypeIcon;
404  } else {
405  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
406  if ($fileExtensionIcon !== 'mimetypes-other-other') {
407  // Fallback 1: icon by file extension
408  $iconIdentifier = $fileExtensionIcon;
409  } else {
410  // Fallback 2: icon by mime type with subtype replaced by *
411  $mimeTypeParts = explode('/', $resource->getMimeType());
412  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
413  if ($mimeTypeIcon !== null) {
414  $iconIdentifier = $mimeTypeIcon;
415  } else {
416  // Fallback 3: use 'mimetypes-other-other'
417  $iconIdentifier = $fileExtensionIcon;
418  }
419  }
420  }
421  if ($resource->isMissing()) {
422  $overlayIdentifier = 'overlay-missing';
423  }
424  }
425 
426  unset($options['mount-root']);
427  unset($options['folder-open']);
428  list($iconIdentifier, $overlayIdentifier) =
429  $this->‪emitBuildIconForResourceSignal($resource, $size, $options, $iconIdentifier, $overlayIdentifier);
430  return $this->‪getIcon($iconIdentifier, $size, $overlayIdentifier);
431  }
432 
442  protected function ‪createIcon($identifier, $size, $overlayIdentifier = null, array $iconConfiguration = [])
443  {
444  $icon = GeneralUtility::makeInstance(Icon::class);
445  $icon->setIdentifier($identifier);
446  $icon->setSize($size);
447  $icon->setState($iconConfiguration['state'] ?: new IconState());
448  if (!empty($overlayIdentifier)) {
449  $icon->setOverlayIcon($this->‪getIcon($overlayIdentifier, ‪Icon::SIZE_OVERLAY));
450  }
451  if (!empty($iconConfiguration['options']['spinning'])) {
452  $icon->setSpinning(true);
453  }
454 
455  return $icon;
456  }
457 
470  protected function ‪emitBuildIconForResourceSignal(
471  ResourceInterface $resource,
472  $size,
473  array $options,
474  $iconIdentifier,
475  $overlayIdentifier
476  ) {
477  $result = $this->‪getSignalSlotDispatcher()->‪dispatch(
478  self::class,
479  'buildIconForResourceSignal',
480  [$resource, $size, $options, $iconIdentifier, $overlayIdentifier]
481  );
482  $iconIdentifier = $result[3];
483  $overlayIdentifier = $result[4];
484  return [$iconIdentifier, $overlayIdentifier];
485  }
486 
492  protected function ‪getSignalSlotDispatcher()
493  {
494  return GeneralUtility::makeInstance(Dispatcher::class);
495  }
496 
500  public function ‪clearIconCache()
501  {
502  static::$iconCache = [];
503  }
504 }
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconRegistry
‪IconRegistry $iconRegistry
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Imaging
Definition: Dimension.php:2
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_DEFAULT
‪const SIZE_DEFAULT
Definition: Icon.php:34
‪TYPO3\CMS\Core\Imaging\IconFactory\clearIconCache
‪clearIconCache()
Definition: IconFactory.php:496
‪TYPO3\CMS\Core\Imaging\IconFactory\emitBuildIconForResourceSignal
‪mixed emitBuildIconForResourceSignal(ResourceInterface $resource, $size, array $options, $iconIdentifier, $overlayIdentifier)
Definition: IconFactory.php:466
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:25
‪TYPO3\CMS\Core\Imaging\IconFactory\$recordStatusMapping
‪string[] $recordStatusMapping
Definition: IconFactory.php:41
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:31
‪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:54
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_OVERLAY
‪const SIZE_OVERLAY
Definition: Icon.php:45
‪TYPO3\CMS\Core\Resource\ResourceStorage\getRootLevelFolder
‪Folder getRootLevelFolder($respectFileMounts=true)
Definition: ResourceStorage.php:2536
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_TEMPORARY
‪const ROLE_TEMPORARY
Definition: FolderInterface.php:28
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForFileExtension
‪Icon getIconForFileExtension($fileExtension, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null)
Definition: IconFactory.php:316
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:182
‪TYPO3\CMS\Core\Resource\InaccessibleFolder
Definition: InaccessibleFolder.php:26
‪TYPO3\CMS\Core\Resource\ResourceStorage\checkFolderActionPermission
‪bool checkFolderActionPermission($action, Folder $folder=null)
Definition: ResourceStorage.php:707
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForResource
‪Icon getIconForResource(ResourceInterface $resource, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, array $options=[])
Definition: IconFactory.php:340
‪TYPO3\CMS\Core\Imaging\IconFactory\createIcon
‪Icon createIcon($identifier, $size, $overlayIdentifier=null, array $iconConfiguration=[])
Definition: IconFactory.php:438
‪TYPO3\CMS\Core\Resource\ResourceStorage\isBrowsable
‪bool isBrowsable()
Definition: ResourceStorage.php:340
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:23
‪TYPO3\CMS\Core\Imaging\IconFactory\getIconForRecord
‪Icon getIconForRecord($table, array $row, $size=Icon::SIZE_DEFAULT)
Definition: IconFactory.php:109
‪TYPO3\CMS\Core\Imaging\IconRegistry
Definition: IconRegistry.php:34
‪TYPO3\CMS\Core\Imaging\IconFactory\$iconCache
‪static array $iconCache
Definition: IconFactory.php:54
‪TYPO3\CMS\Extbase\SignalSlot\Dispatcher\dispatch
‪mixed dispatch($signalClassName, $signalName, array $signalArguments=[])
Definition: Dispatcher.php:115
‪TYPO3\CMS\Core\Imaging\IconFactory\$overlayPriorities
‪string[] $overlayPriorities
Definition: IconFactory.php:48
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_USER_MOUNT
‪const ROLE_USER_MOUNT
Definition: FolderInterface.php:32
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:23
‪TYPO3\CMS\Core\Type\Icon\IconState
Definition: IconState.php:23
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_READONLY_MOUNT
‪const ROLE_READONLY_MOUNT
Definition: FolderInterface.php:31
‪$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:26
‪TYPO3\CMS\Core\Resource\FolderInterface
Definition: FolderInterface.php:21
‪TYPO3\CMS\Core\Resource\Folder\getIdentifier
‪string getIdentifier()
Definition: Folder.php:157
‪TYPO3\CMS\Core\Imaging\IconFactory\__construct
‪__construct(IconRegistry $iconRegistry=null)
Definition: IconFactory.php:59
‪TYPO3\CMS\Core\Resource\ResourceInterface
Definition: ResourceInterface.php:21
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToIconIdentifier
‪string mapRecordTypeToIconIdentifier($table, array $row)
Definition: IconFactory.php:132
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Imaging\IconFactory\getIcon
‪Icon getIcon($identifier, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, IconState $state=null)
Definition: IconFactory.php:73
‪TYPO3\CMS\Core\Imaging\IconFactory\mapRecordTypeToOverlayIdentifier
‪string mapRecordTypeToOverlayIdentifier($table, array $row)
Definition: IconFactory.php:233
‪TYPO3\CMS\Core\Imaging\IconFactory\getSignalSlotDispatcher
‪TYPO3 CMS Extbase SignalSlot Dispatcher getSignalSlotDispatcher()
Definition: IconFactory.php:488
‪TYPO3\CMS\Extbase\SignalSlot\Dispatcher
Definition: Dispatcher.php:28