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