TYPO3 CMS  TYPO3_8-7
IconFactory.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Imaging;
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 
27 
33 {
37  protected $iconRegistry;
38 
45  protected $recordStatusMapping = [];
46 
53  protected $overlayPriorities = [];
54 
60  protected static $iconCache = [];
61 
65  public function __construct(IconRegistry $iconRegistry = null)
66  {
67  $this->iconRegistry = $iconRegistry ? $iconRegistry : GeneralUtility::makeInstance(IconRegistry::class);
68  $this->recordStatusMapping = $GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['recordStatusMapping'];
69  $this->overlayPriorities = $GLOBALS['TYPO3_CONF_VARS']['SYS']['IconFactory']['overlayPriorities'];
70  }
71 
78  public function processAjaxRequest(ServerRequestInterface $request, ResponseInterface $response)
79  {
80  $parsedBody = $request->getParsedBody();
81  $queryParams = $request->getQueryParams();
82  $requestedIcon = json_decode(
83  isset($parsedBody['icon']) ? $parsedBody['icon'] : $queryParams['icon'],
84  true
85  );
86 
87  list($identifier, $size, $overlayIdentifier, $iconState, $alternativeMarkupIdentifier) = $requestedIcon;
88  if (empty($overlayIdentifier)) {
89  $overlayIdentifier = null;
90  }
91  $iconState = IconState::cast($iconState);
92  $response->getBody()->write(
93  $this->getIcon($identifier, $size, $overlayIdentifier, $iconState)->render($alternativeMarkupIdentifier)
94  );
95  $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
96  return $response;
97  }
98 
106  public function getIcon($identifier, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null, IconState $state = null)
107  {
108  $cacheIdentifier = md5($identifier . $size . $overlayIdentifier . (string)$state);
109  if (!empty(static::$iconCache[$cacheIdentifier])) {
110  return static::$iconCache[$cacheIdentifier];
111  }
112  if (!$this->iconRegistry->isRegistered($identifier)) {
113  $identifier = $this->iconRegistry->getDefaultIconIdentifier();
114  }
115 
116  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
117  $iconConfiguration['state'] = $state;
118  $icon = $this->createIcon($identifier, $size, $overlayIdentifier, $iconConfiguration);
119 
121  $iconProvider = GeneralUtility::makeInstance($iconConfiguration['provider']);
122  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
123 
124  static::$iconCache[$cacheIdentifier] = $icon;
125 
126  return $icon;
127  }
128 
137  public function getIconForRecord($table, array $row, $size = Icon::SIZE_DEFAULT)
138  {
139  $iconIdentifier = $this->mapRecordTypeToIconIdentifier($table, $row);
140  $overlayIdentifier = $this->mapRecordTypeToOverlayIdentifier($table, $row);
141  return $this->getIcon($iconIdentifier, $size, $overlayIdentifier);
142  }
143 
160  public function mapRecordTypeToIconIdentifier($table, array $row)
161  {
162  $recordType = [];
163  $ref = null;
164 
165  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
166  $column = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
167  if (isset($row[$column])) {
168  // even if not properly documented the value of the typeicon_column in a record could be
169  // an array (multiselect) in typeicon_classes a key could consist of a comma-separated string "foo,bar"
170  // but mostly it should be only one entry in that array
171  if (is_array($row[$column])) {
172  $recordType[1] = implode(',', $row[$column]);
173  } else {
174  $recordType[1] = $row[$column];
175  }
176  } else {
177  $recordType[1] = 'default';
178  }
179  // Workaround to give nav_hide pages a complete different icon
180  // Although it's not a separate doctype
181  // and to give root-pages an own icon
182  if ($table === 'pages') {
183  if ((int)$row['nav_hide'] > 0) {
184  $recordType[2] = $recordType[1] . '-hideinmenu';
185  }
186  if ((int)$row['is_siteroot'] > 0) {
187  $recordType[3] = $recordType[1] . '-root';
188  }
189  if (!empty($row['module'])) {
190  $recordType[4] = 'contains-' . $row['module'];
191  }
192  if ((int)$row['content_from_pid'] > 0) {
193  if ($row['is_siteroot']) {
194  $recordType[4] = 'page-contentFromPid-root';
195  } else {
196  $recordType[4] = (int)$row['nav_hide'] === 0
197  ? 'page-contentFromPid' : 'page-contentFromPid-hideinmenu';
198  }
199  }
200  }
201  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
202  && is_array($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
203  ) {
204  foreach ($recordType as $key => $type) {
205  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
206  $recordType[$key] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
207  } else {
208  unset($recordType[$key]);
209  }
210  }
211  $recordType[0] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
212  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])) {
213  $recordType[5] = str_replace(
214  '###TYPE###',
215  $row[$column],
216  $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']
217  );
218  }
219  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
220  $parameters = ['row' => $row];
221  $recordType[6] = GeneralUtility::callUserFunction(
222  $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'],
223  $parameters,
224  $ref
225  );
226  }
227  } else {
228  foreach ($recordType as &$type) {
229  $type = 'tcarecords-' . $table . '-' . $type;
230  }
231  unset($type);
232  $recordType[0] = 'tcarecords-' . $table . '-default';
233  }
234  } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
235  && is_array($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])
236  ) {
237  $recordType[0] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
238  } else {
239  $recordType[0] = 'tcarecords-' . $table . '-default';
240  }
241 
242  krsort($recordType);
243  foreach ($recordType as $iconName) {
244  if ($this->iconRegistry->isRegistered($iconName)) {
245  return $iconName;
246  }
247  }
248 
249  return $this->iconRegistry->getDefaultIconIdentifier();
250  }
251 
261  protected function mapRecordTypeToOverlayIdentifier($table, array $row)
262  {
263  $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
264  // Calculate for a given record the actual visibility at the moment
265  $status = [
266  'hidden' => false,
267  'starttime' => false,
268  'endtime' => false,
269  'futureendtime' => false,
270  'fe_group' => false,
271  'deleted' => false,
272  'protectedSection' => false,
273  'nav_hide' => !empty($row['nav_hide']),
274  ];
275  // Icon state based on "enableFields":
276  if (isset($tcaCtrl['enablecolumns']) && is_array($tcaCtrl['enablecolumns'])) {
277  $enableColumns = $tcaCtrl['enablecolumns'];
278  // If "hidden" is enabled:
279  if (isset($enableColumns['disabled']) && !empty($row[$enableColumns['disabled']])) {
280  $status['hidden'] = true;
281  }
282  // If a "starttime" is set and higher than current time:
283  if (!empty($enableColumns['starttime']) && $GLOBALS['EXEC_TIME'] < (int)$row[$enableColumns['starttime']]) {
284  $status['starttime'] = true;
285  }
286  // If an "endtime" is set
287  if (!empty($enableColumns['endtime'])) {
288  if ((int)$row[$enableColumns['endtime']] > 0) {
289  if ((int)$row[$enableColumns['endtime']] < $GLOBALS['EXEC_TIME']) {
290  // End-timing applies at this point.
291  $status['endtime'] = true;
292  } else {
293  // End-timing WILL apply in the future for this element.
294  $status['futureendtime'] = true;
295  }
296  }
297  }
298  // If a user-group field is set
299  if (!empty($enableColumns['fe_group']) && $row[$enableColumns['fe_group']]) {
300  $status['fe_group'] = true;
301  }
302  }
303  // If "deleted" flag is set (only when listing records which are also deleted!)
304  if (isset($tcaCtrl['delete']) && !empty($row[$tcaCtrl['delete']])) {
305  $status['deleted'] = true;
306  }
307  // Detecting extendToSubpages (for pages only)
308  if ($table === 'pages' && (int)$row['extendToSubpages'] > 0) {
309  $status['protectedSection'] = true;
310  }
311  if (isset($row['t3ver_state'])
312  && VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
313  $status['deleted'] = true;
314  }
315 
316  // Now only show the status with the highest priority
317  $iconName = '';
318  foreach ($this->overlayPriorities as $priority) {
319  if ($status[$priority]) {
320  $iconName = $this->recordStatusMapping[$priority];
321  break;
322  }
323  }
324 
325  // Hook to define an alternative iconName
326  if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['overrideIconOverlay'])) {
327  $hookObjects = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['overrideIconOverlay'];
328  foreach ($hookObjects as $classRef) {
329  $hookObject = GeneralUtility::getUserObj($classRef);
330  if (method_exists($hookObject, 'postOverlayPriorityLookup')) {
331  $iconName = $hookObject->postOverlayPriorityLookup($table, $row, $status, $iconName);
332  }
333  }
334  }
335 
336  return $iconName;
337  }
338 
347  public function getIconForFileExtension($fileExtension, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null)
348  {
349  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
350  return $this->getIcon($iconName, $size, $overlayIdentifier);
351  }
352 
371  public function getIconForResource(
372  ResourceInterface $resource,
373  $size = Icon::SIZE_DEFAULT,
374  $overlayIdentifier = null,
375  array $options = []
376  ) {
377  $iconIdentifier = null;
378 
379  // Folder
380  if ($resource instanceof FolderInterface) {
381  // non browsable storage
382  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
383  $iconIdentifier = 'apps-filetree-folder-locked';
384  } else {
385  // storage root
386  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
387  $iconIdentifier = 'apps-filetree-root';
388  }
389 
390  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
391 
392  // user/group mount root
393  if (!empty($options['mount-root'])) {
394  $iconIdentifier = 'apps-filetree-mount';
395  if ($role === FolderInterface::ROLE_READONLY_MOUNT) {
396  $overlayIdentifier = 'overlay-locked';
397  } elseif ($role === FolderInterface::ROLE_USER_MOUNT) {
398  $overlayIdentifier = 'overlay-restricted';
399  }
400  }
401 
402  if ($iconIdentifier === null) {
403  // in folder tree view $options['folder-open'] can define an open folder icon
404  if (!empty($options['folder-open'])) {
405  $iconIdentifier = 'apps-filetree-folder-opened';
406  } else {
407  $iconIdentifier = 'apps-filetree-folder-default';
408  }
409 
410  if ($role === FolderInterface::ROLE_TEMPORARY) {
411  $iconIdentifier = 'apps-filetree-folder-temp';
412  } elseif ($role === FolderInterface::ROLE_RECYCLER) {
413  $iconIdentifier = 'apps-filetree-folder-recycler';
414  }
415  }
416 
417  // if locked add overlay
418  if ($resource instanceof InaccessibleFolder ||
419  !$resource->getStorage()->isBrowsable() ||
420  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
421  ) {
422  $overlayIdentifier = 'overlay-locked';
423  }
424  }
425  } elseif ($resource instanceof File) {
426  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
427 
428  // Check if we find a exact matching mime type
429  if ($mimeTypeIcon !== null) {
430  $iconIdentifier = $mimeTypeIcon;
431  } else {
432  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
433  if ($fileExtensionIcon !== 'mimetypes-other-other') {
434  // Fallback 1: icon by file extension
435  $iconIdentifier = $fileExtensionIcon;
436  } else {
437  // Fallback 2: icon by mime type with subtype replaced by *
438  $mimeTypeParts = explode('/', $resource->getMimeType());
439  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
440  if ($mimeTypeIcon !== null) {
441  $iconIdentifier = $mimeTypeIcon;
442  } else {
443  // Fallback 3: use 'mimetypes-other-other'
444  $iconIdentifier = $fileExtensionIcon;
445  }
446  }
447  }
448  if ($resource->isMissing()) {
449  $overlayIdentifier = 'overlay-missing';
450  }
451  }
452 
453  unset($options['mount-root']);
454  unset($options['folder-open']);
455  list($iconIdentifier, $overlayIdentifier) =
456  $this->emitBuildIconForResourceSignal($resource, $size, $options, $iconIdentifier, $overlayIdentifier);
457  return $this->getIcon($iconIdentifier, $size, $overlayIdentifier);
458  }
459 
469  protected function createIcon($identifier, $size, $overlayIdentifier = null, array $iconConfiguration = [])
470  {
471  $icon = GeneralUtility::makeInstance(Icon::class);
472  $icon->setIdentifier($identifier);
473  $icon->setSize($size);
474  $icon->setState($iconConfiguration['state'] ?: new IconState());
475  if (!empty($overlayIdentifier)) {
476  $icon->setOverlayIcon($this->getIcon($overlayIdentifier, Icon::SIZE_OVERLAY));
477  }
478  if (!empty($iconConfiguration['options']['spinning'])) {
479  $icon->setSpinning(true);
480  }
481 
482  return $icon;
483  }
484 
497  protected function emitBuildIconForResourceSignal(
498  ResourceInterface $resource,
499  $size,
500  array $options,
501  $iconIdentifier,
502  $overlayIdentifier
503  ) {
504  $result = $this->getSignalSlotDispatcher()->dispatch(
505  self::class,
506  'buildIconForResourceSignal',
507  [$resource, $size, $options, $iconIdentifier, $overlayIdentifier]
508  );
509  $iconIdentifier = $result[3];
510  $overlayIdentifier = $result[4];
511  return [$iconIdentifier, $overlayIdentifier];
512  }
513 
519  protected function getSignalSlotDispatcher()
520  {
521  return GeneralUtility::makeInstance(Dispatcher::class);
522  }
523 
527  public function clearIconCache()
528  {
529  static::$iconCache = [];
530  }
531 }
__construct(IconRegistry $iconRegistry=null)
Definition: IconFactory.php:65
createIcon($identifier, $size, $overlayIdentifier=null, array $iconConfiguration=[])
processAjaxRequest(ServerRequestInterface $request, ResponseInterface $response)
Definition: IconFactory.php:78
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
mapRecordTypeToOverlayIdentifier($table, array $row)
getIconForRecord($table, array $row, $size=Icon::SIZE_DEFAULT)
getIconForFileExtension($fileExtension, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null)
static makeInstance($className,... $constructorArguments)
getIconForResource(ResourceInterface $resource, $size=Icon::SIZE_DEFAULT, $overlayIdentifier=null, array $options=[])
mapRecordTypeToIconIdentifier($table, array $row)
emitBuildIconForResourceSignal(ResourceInterface $resource, $size, array $options, $iconIdentifier, $overlayIdentifier)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']