TYPO3  7.6
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 
44  protected $recordStatusMapping = array(
45  'hidden' => 'overlay-hidden',
46  'fe_group' => 'overlay-restricted',
47  'starttime' => 'overlay-scheduled',
48  'endtime' => 'overlay-scheduled',
49  'futureendtime' => 'overlay-scheduled',
50  'readonly' => 'overlay-readonly',
51  'deleted' => 'overlay-deleted',
52  'missing' => 'overlay-missing',
53  'translated' => 'overlay-translated',
54  'protectedSection' => 'overlay-includes-subpages'
55  );
56 
62  protected $overlayPriorities = array(
63  'hidden',
64  'starttime',
65  'endtime',
66  'futureendtime',
67  'protectedSection',
68  'fe_group'
69  );
70 
74  public function __construct(IconRegistry $iconRegistry = null)
75  {
76  $this->iconRegistry = $iconRegistry ? $iconRegistry : GeneralUtility::makeInstance(IconRegistry::class);
77  }
78 
86  {
87  $parsedBody = $request->getParsedBody();
88  $queryParams = $request->getQueryParams();
89  $requestedIcon = json_decode(
90  isset($parsedBody['icon'])
91  ? $parsedBody['icon']
92  : $queryParams['icon'],
93  true
94  );
95 
96  list($identifier, $size, $overlayIdentifier, $iconState) = $requestedIcon;
97  if (empty($overlayIdentifier)) {
98  $overlayIdentifier = null;
99  }
100  $iconState = IconState::cast($iconState);
101  $response->getBody()->write(
102  $this->getIcon($identifier, $size, $overlayIdentifier, $iconState)->render()
103  );
104  $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
105  return $response;
106  }
107 
115  public function getIcon($identifier, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null, IconState $state = null)
116  {
117  if ($this->iconRegistry->isDeprecated($identifier)) {
118  $deprecationSettings = $this->iconRegistry->getDeprecationSettings($identifier);
119  GeneralUtility::deprecationLog(sprintf($deprecationSettings['message'], $identifier));
120  if (!empty($deprecationSettings['replacement'])) {
121  $identifier = $deprecationSettings['replacement'];
122  }
123  }
124  if (!$this->iconRegistry->isRegistered($identifier)) {
125  $identifier = $this->iconRegistry->getDefaultIconIdentifier();
126  }
127 
128  $iconConfiguration = $this->iconRegistry->getIconConfigurationByIdentifier($identifier);
129  $iconConfiguration['state'] = $state;
130  $icon = $this->createIcon($identifier, $size, $overlayIdentifier, $iconConfiguration);
131 
133  $iconProvider = GeneralUtility::makeInstance($iconConfiguration['provider']);
134  $iconProvider->prepareIconMarkup($icon, $iconConfiguration['options']);
135 
136  return $icon;
137  }
138 
147  public function getIconForRecord($table, array $row, $size = Icon::SIZE_DEFAULT)
148  {
149  $iconIdentifier = $this->mapRecordTypeToIconIdentifier($table, $row);
150  $overlayIdentifier = $this->mapRecordTypeToOverlayIdentifier($table, $row);
151  if (empty($overlayIdentifier)) {
152  $overlayIdentifier = null;
153  }
154  return $this->getIcon($iconIdentifier, $size, $overlayIdentifier);
155  }
156 
173  public function mapRecordTypeToIconIdentifier($table, array $row)
174  {
175  $recordType = array();
176  $ref = null;
177 
178  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_column'])) {
179  $column = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
180  if (isset($row[$column])) {
181  // even if not properly documented the value of the typeicon_column in a record could be an array (multiselect)
182  // in typeicon_classes a key could consist of a commaseparated string "foo,bar"
183  // but mostly it should be only one entry in that array
184  if (is_array($row[$column])) {
185  $recordType[1] = implode(',', $row[$column]);
186  } else {
187  $recordType[1] = $row[$column];
188  }
189  } else {
190  $recordType[1] = 'default';
191  }
192  // Workaround to give nav_hide pages a complete different icon
193  // Although it's not a separate doctype
194  // and to give root-pages an own icon
195  if ($table === 'pages') {
196  if ((int)$row['nav_hide'] > 0) {
197  $recordType[2] = $recordType[1] . '-hideinmenu';
198  }
199  if ((int)$row['is_siteroot'] > 0) {
200  $recordType[3] = $recordType[1] . '-root';
201  }
202  if (!empty($row['module'])) {
203  $recordType[4] = 'contains-' . $row['module'];
204  }
205  if ((int)$row['content_from_pid'] > 0) {
206  if ($row['is_siteroot']) {
207  $recordType[4] = 'page-contentFromPid-root';
208  } else {
209  $recordType[4] = (int)$row['nav_hide'] === 0 ? 'page-contentFromPid' : 'page-contentFromPid-hideinmenu';
210  }
211  }
212  }
213  if (is_array($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])) {
214  foreach ($recordType as $key => $type) {
215  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type])) {
216  $recordType[$key] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'][$type];
217  } else {
218  unset($recordType[$key]);
219  }
220  }
221  $recordType[0] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['default'];
222  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask'])) {
223  $recordType[5] = str_replace('###TYPE###', $row[$column], $GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['mask']);
224  }
225  if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'])) {
226  $parameters = array('row' => $row);
227  $recordType[6] = GeneralUtility::callUserFunction($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes']['userFunc'], $parameters, $ref);
228  }
229  } else {
230  foreach ($recordType as &$type) {
231  $type = 'tcarecords-' . $table . '-' . $type;
232  }
233  unset($type);
234  $recordType[0] = 'tcarecords-' . $table . '-default';
235  }
236  } elseif (is_array($GLOBALS['TCA'][$table]['ctrl']['typeicon_classes'])) {
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 = array(
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' => (bool)$row['nav_hide']
274  );
275  // Icon state based on "enableFields":
276  if (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']) && 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  return $iconName;
325  }
326 
335  public function getIconForFileExtension($fileExtension, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null)
336  {
337  $iconName = $this->iconRegistry->getIconIdentifierForFileExtension($fileExtension);
338  return $this->getIcon($iconName, $size, $overlayIdentifier);
339  }
340 
359  public function getIconForResource(ResourceInterface $resource, $size = Icon::SIZE_DEFAULT, $overlayIdentifier = null, array $options = array())
360  {
361  $iconIdentifier = null;
362 
363  // Folder
364  if ($resource instanceof FolderInterface) {
365  // non browsable storage
366  if ($resource->getStorage()->isBrowsable() === false && !empty($options['mount-root'])) {
367  $iconIdentifier = 'apps-filetree-folder-locked';
368  } else {
369  // storage root
370  if ($resource->getStorage()->getRootLevelFolder()->getIdentifier() === $resource->getIdentifier()) {
371  $iconIdentifier = 'apps-filetree-root';
372  }
373 
374  $role = is_callable([$resource, 'getRole']) ? $resource->getRole() : '';
375 
376  // user/group mount root
377  if (!empty($options['mount-root'])) {
378  $iconIdentifier = 'apps-filetree-mount';
379  if ($role === FolderInterface::ROLE_READONLY_MOUNT) {
380  $overlayIdentifier = 'overlay-locked';
382  $overlayIdentifier = 'overlay-restricted';
383  }
384  }
385 
386  if ($iconIdentifier === null) {
387  // in folder tree view $options['folder-open'] can define an open folder icon
388  if (!empty($options['folder-open'])) {
389  $iconIdentifier = 'apps-filetree-folder-opened';
390  } else {
391  $iconIdentifier = 'apps-filetree-folder-default';
392  }
393 
394  if ($role === FolderInterface::ROLE_TEMPORARY) {
395  $iconIdentifier = 'apps-filetree-folder-temp';
396  } elseif ($role === FolderInterface::ROLE_RECYCLER) {
397  $iconIdentifier = 'apps-filetree-folder-recycler';
398  }
399  }
400 
401  // if locked add overlay
402  if ($resource instanceof InaccessibleFolder ||
403  !$resource->getStorage()->isBrowsable() ||
404  !$resource->getStorage()->checkFolderActionPermission('add', $resource)
405  ) {
406  $overlayIdentifier = 'overlay-locked';
407  }
408  }
409 
410  // File
411  } elseif ($resource instanceof File) {
412  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($resource->getMimeType());
413 
414  // Check if we find a exact matching mime type
415  if ($mimeTypeIcon !== null) {
416  $iconIdentifier = $mimeTypeIcon;
417  } else {
418  $fileExtensionIcon = $this->iconRegistry->getIconIdentifierForFileExtension($resource->getExtension());
419  if ($fileExtensionIcon !== 'mimetypes-other-other') {
420  // Fallback 1: icon by file extension
421  $iconIdentifier = $fileExtensionIcon;
422  } else {
423  // Fallback 2: icon by mime type with subtype replaced by *
424  $mimeTypeParts = explode('/', $resource->getMimeType());
425  $mimeTypeIcon = $this->iconRegistry->getIconIdentifierForMimeType($mimeTypeParts[0] . '/*');
426  if ($mimeTypeIcon !== null) {
427  $iconIdentifier = $mimeTypeIcon;
428  } else {
429  // Fallback 3: use 'mimetypes-other-other'
430  $iconIdentifier = $fileExtensionIcon;
431  }
432  }
433  }
434  if ($resource->isMissing()) {
435  $overlayIdentifier = 'overlay-missing';
436  }
437  }
438 
439  unset($options['mount-root']);
440  unset($options['folder-open']);
441  list($iconIdentifier, $overlayIdentifier) = $this->emitBuildIconForResourceSignal($resource, $size, $options, $iconIdentifier, $overlayIdentifier);
442  return $this->getIcon($iconIdentifier, $size, $overlayIdentifier);
443  }
444 
454  protected function createIcon($identifier, $size, $overlayIdentifier = null, array $iconConfiguration)
455  {
456  $icon = GeneralUtility::makeInstance(Icon::class);
457  $icon->setIdentifier($identifier);
458  $icon->setSize($size);
459  $icon->setState($iconConfiguration['state'] ?: new IconState());
460  if ($overlayIdentifier !== null) {
461  $icon->setOverlayIcon($this->getIcon($overlayIdentifier, Icon::SIZE_OVERLAY));
462  }
463  if (!empty($iconConfiguration['options']['spinning'])) {
464  $icon->setSpinning(true);
465  }
466 
467  return $icon;
468  }
469 
482  protected function emitBuildIconForResourceSignal(ResourceInterface $resource, $size, array $options, $iconIdentifier, $overlayIdentifier)
483  {
484  $result = $this->getSignalSlotDispatcher()->dispatch(IconFactory::class, 'buildIconForResourceSignal', array($resource, $size, $options, $iconIdentifier, $overlayIdentifier));
485  $iconIdentifier = $result[3];
486  $overlayIdentifier = $result[4];
487  return array($iconIdentifier, $overlayIdentifier);
488  }
489 
495  protected function getSignalSlotDispatcher()
496  {
497  return GeneralUtility::makeInstance(Dispatcher::class);
498  }
499 }