TYPO3 CMS  TYPO3_8-7
RecordProvider.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
23 
28 {
34  protected $record = [];
35 
41  protected $pageRecord = [];
42 
48  protected $pagePermissions = 0;
49 
53  protected $itemsConfiguration = [
54  'view' => [
55  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view',
56  'iconIdentifier' => 'actions-document-view',
57  'callbackAction' => 'viewRecord'
58  ],
59  'edit' => [
60  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit',
61  'iconIdentifier' => 'actions-open',
62  'callbackAction' => 'editRecord'
63  ],
64  'new' => [
65  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new',
66  'iconIdentifier' => 'actions-document-new',
67  'callbackAction' => 'newRecord'
68  ],
69  'info' => [
70  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info',
71  'iconIdentifier' => 'actions-document-info',
72  'callbackAction' => 'openInfoPopUp'
73  ],
74  'divider1' => [
75  'type' => 'divider'
76  ],
77  'copy' => [
78  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
79  'iconIdentifier' => 'actions-edit-copy',
80  'callbackAction' => 'copy'
81  ],
82  'copyRelease' => [
83  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
84  'iconIdentifier' => 'actions-edit-copy-release',
85  'callbackAction' => 'clipboardRelease'
86  ],
87  'cut' => [
88  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
89  'iconIdentifier' => 'actions-edit-cut',
90  'callbackAction' => 'cut'
91  ],
92  'cutRelease' => [
93  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
94  'iconIdentifier' => 'actions-edit-cut-release',
95  'callbackAction' => 'clipboardRelease'
96  ],
97  'pasteAfter' => [
98  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteafter',
99  'iconIdentifier' => 'actions-document-paste-after',
100  'callbackAction' => 'pasteAfter'
101  ],
102  'divider2' => [
103  'type' => 'divider'
104  ],
105  'more' => [
106  'type' => 'submenu',
107  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.more',
108  'iconIdentifier' => '',
109  'callbackAction' => 'openSubmenu',
110  'childItems' => [
111  'newWizard' => [
112  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_newWizard',
113  'iconIdentifier' => 'actions-document-new',
114  'callbackAction' => 'newContentWizard',
115  ],
116  'openListModule' => [
117  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_db_list',
118  'iconIdentifier' => 'actions-system-list-open',
119  'callbackAction' => 'openListModule',
120  ],
121  ],
122  ],
123  'divider3' => [
124  'type' => 'divider'
125  ],
126  'enable' => [
127  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enable',
128  'iconIdentifier' => 'actions-edit-unhide',
129  'callbackAction' => 'enableRecord',
130  ],
131  'disable' => [
132  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable',
133  'iconIdentifier' => 'actions-edit-hide',
134  'callbackAction' => 'disableRecord',
135  ],
136  'delete' => [
137  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete',
138  'iconIdentifier' => 'actions-edit-delete',
139  'callbackAction' => 'deleteRecord',
140  ],
141  'history' => [
142  'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history',
143  'iconIdentifier' => 'actions-document-history-open',
144  'callbackAction' => 'openHistoryPopUp',
145  ],
146  ];
147 
153  public function canHandle(): bool
154  {
155  if (in_array($this->table, ['sys_file', 'sys_filemounts', 'sys_file_storage', 'pages'], true)
156  || strpos($this->table, '-drag') !== false) {
157  return false;
158  }
159  return isset($GLOBALS['TCA'][$this->table]);
160  }
161 
165  protected function initialize()
166  {
167  parent::initialize();
168  $this->record = BackendUtility::getRecordWSOL($this->table, $this->identifier);
169  $this->initPermissions();
170  }
171 
177  public function getPriority(): int
178  {
179  return 60;
180  }
181 
188  public function addItems(array $items): array
189  {
190  if (!empty($items)) {
191  return $items;
192  }
193  $this->initialize();
194  return $this->prepareItems($this->itemsConfiguration);
195  }
196 
204  protected function canRender(string $itemName, string $type): bool
205  {
206  if (in_array($type, ['divider', 'submenu'], true)) {
207  return true;
208  }
209  if (in_array($itemName, $this->disabledItems, true)) {
210  return false;
211  }
212  $canRender = false;
213  switch ($itemName) {
214  case 'view':
215  $canRender = $this->canBeViewed();
216  break;
217  case 'edit':
218  case 'new':
219  $canRender = $this->canBeEdited();
220  break;
221  case 'newWizard':
222  $canRender = $this->canOpenNewCEWizard();
223  break;
224  case 'info':
225  $canRender = $this->canShowInfo();
226  break;
227  case 'enable':
228  $canRender = $this->canBeEnabled();
229  break;
230  case 'disable':
231  $canRender = $this->canBeDisabled();
232  break;
233  case 'delete':
234  $canRender = $this->canBeDeleted();
235  break;
236  case 'history':
237  $canRender = $this->canShowHistory();
238  break;
239  case 'openListModule':
240  $canRender = $this->canOpenListModule();
241  break;
242  case 'copy':
243  $canRender = $this->canBeCopied();
244  break;
245  case 'copyRelease':
246  $canRender = $this->isRecordInClipboard('copy');
247  break;
248  case 'cut':
249  $canRender = $this->canBeCut();
250  break;
251  case 'cutRelease':
252  $canRender = $this->isRecordInClipboard('cut');
253  break;
254  case 'pasteAfter':
255  $canRender = $this->canBePastedAfter();
256  break;
257  }
258  return $canRender;
259  }
260 
264  protected function initPermissions()
265  {
266  $this->pageRecord = BackendUtility::getRecord('pages', $this->record['pid']);
267  $this->pagePermissions = $this->backendUser->calcPerms($this->pageRecord);
268  }
269 
277  protected function hasPagePermission(int $permission): bool
278  {
279  return $this->backendUser->isAdmin() || ($this->pagePermissions & $permission) == $permission;
280  }
281 
288  protected function getAdditionalAttributes(string $itemName): array
289  {
290  $attributes = [];
291  if ($itemName === 'view') {
292  $attributes += $this->getViewAdditionalAttributes();
293  }
294  if ($itemName === 'newWizard' && $this->table === 'tt_content') {
295  $tsConfig = BackendUtility::getModTSconfig($this->record['pid'], 'mod');
296  $moduleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
297  ? $tsConfig['properties']['newContentElementWizard.']['override']
298  : 'new_content_element';
299  $urlParameters = [
300  'id' => $this->record['pid'],
301  'sys_language_uid' => $this->record['sys_language_uid'],
302  'colPos' => $this->record['colPos'],
303  'uid_pid' => -$this->record['uid']
304  ];
305  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
306  $attributes += [
307  'data-new-wizard-url' => htmlspecialchars($url)
308  ];
309  }
310  if ($itemName === 'delete') {
311  $attributes += $this->getDeleteAdditionalAttributes();
312  }
313  if ($itemName === 'openListModule') {
314  $attributes += [
315  'data-page-uid' => $this->record['pid']
316  ];
317  }
318  if ($itemName === 'pasteAfter') {
319  $attributes += $this->getPasteAdditionalAttributes('after');
320  }
321  return $attributes;
322  }
323 
329  protected function getViewAdditionalAttributes(): array
330  {
331  $attributes = [];
332  $viewLink = $this->getViewLink();
333  if ($viewLink) {
334  $attributes += [
335  'data-preview-url' => htmlspecialchars($viewLink),
336  ];
337  }
338  return $attributes;
339  }
340 
347  protected function getPasteAdditionalAttributes(string $type): array
348  {
349  $attributes = [];
350  if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
351  $selItem = $this->clipboard->getSelectedRecord();
352  $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
353 
354  $confirmMessage = sprintf(
355  $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.'
356  . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_' . $type),
357  GeneralUtility::fixed_lgd_cs($selItem['_RECORD_TITLE'], $this->backendUser->uc['titleLen']),
358  GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($this->table, $this->record), $this->backendUser->uc['titleLen'])
359  );
360  $attributes += [
361  'data-title' => htmlspecialchars($title),
362  'data-message' => htmlspecialchars($confirmMessage)
363  ];
364  }
365  return $attributes;
366  }
367 
373  protected function getDeleteAdditionalAttributes(): array
374  {
375  $attributes = [];
376  if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
377  $recordTitle = GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($this->table, $this->record), $this->backendUser->uc['titleLen']);
378 
379  $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
380  $confirmMessage = sprintf(
381  $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'),
382  $recordTitle
383  );
384  $confirmMessage .= BackendUtility::referenceCount(
385  $this->table,
386  $this->record['uid'],
387  ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord')
388  );
389  $confirmMessage .= BackendUtility::translationCount(
390  $this->table,
391  $this->record['uid'],
392  ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
393  );
394  $attributes += [
395  'data-title' => htmlspecialchars($title),
396  'data-message' => htmlspecialchars($confirmMessage)
397  ];
398  }
399  return $attributes;
400  }
401 
407  protected function getPreviewPid(): int
408  {
409  return (int)$this->record['pid'];
410  }
411 
417  protected function getViewLink(): string
418  {
419  $additionalParams = '';
420  if ($this->table === 'tt_content') {
421  $language = (int)$this->record[$GLOBALS['TCA']['tt_content']['ctrl']['languageField']];
422  if ($language > 0) {
423  $additionalParams = '&L=' . $language;
424  }
425  }
426  $javascriptLink = BackendUtility::viewOnClick(
427  $this->getPreviewPid(),
428  '',
429  null,
430  '',
431  '',
432  $additionalParams
433  );
434  $extractedLink = '';
435  if (preg_match('/window\\.open\\(\'([^\']+)\'/i', $javascriptLink, $match)) {
436  // Clean JSON-serialized ampersands ('&')
437  // @see GeneralUtility::quoteJSvalue()
438  $extractedLink = json_decode('"' . trim($match[1], '"') . '"');
439  }
440  return $extractedLink;
441  }
442 
448  protected function canShowInfo(): bool
449  {
450  return true;
451  }
452 
458  protected function canShowHistory(): bool
459  {
460  $showHistoryTS = $this->backendUser->getTSConfig('options.showHistory');
461  $showHistory = (bool)trim($showHistoryTS['properties'][$this->table] ?? $showHistoryTS['value'] ?? '1');
462  return $showHistory;
463  }
464 
470  protected function canBeViewed(): bool
471  {
472  return $this->table === 'tt_content';
473  }
474 
480  protected function canBeEdited(): bool
481  {
482  if (isset($GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) {
483  return false;
484  }
485  if ($this->backendUser->isAdmin()) {
486  return true;
487  }
488  if (isset($GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) {
489  return false;
490  }
491 
492  $access = !$this->isRecordLocked()
493  && $this->backendUser->check('tables_modify', $this->table)
495  && $this->backendUser->recordEditAccessInternals($this->table, $this->record);
496  return $access;
497  }
498 
504  protected function isDeletionDisabledInTS(): bool
505  {
506  $disableDeleteTS = $this->backendUser->getTSConfig('options.disableDelete');
507  $disableDelete = (bool)trim($disableDeleteTS['properties'][$this->table] ?? (string)$disableDeleteTS['value']);
508  return $disableDelete;
509  }
510 
516  protected function canBeDeleted(): bool
517  {
518  return !$this->isDeletionDisabledInTS() && $this->canBeEdited();
519  }
520 
526  protected function canBeEnabled(): bool
527  {
528  return $this->hasDisableColumnWithValue(1) && $this->canBeEdited();
529  }
530 
536  protected function canBeDisabled(): bool
537  {
538  return $this->hasDisableColumnWithValue(0) && $this->canBeEdited();
539  }
540 
546  protected function canOpenNewCEWizard(): bool
547  {
548  $tsConfig = BackendUtility::getModTSconfig($this->record['pid'], 'mod.web_layout');
549  $wizardEnabled = true;
550  if (isset($tsConfig['properties']['disableNewContentElementWizard'])) {
551  $wizardEnabled = false;
552  }
553  return $this->table === 'tt_content' && $wizardEnabled && $this->canBeEdited();
554  }
555 
559  protected function canOpenListModule(): bool
560  {
561  return $this->backendUser->check('modules', 'web_list');
562  }
563 
567  protected function canBeCopied(): bool
568  {
569  return !$this->isRecordInClipboard('copy')
570  && !$this->isRecordATranslation();
571  }
572 
576  protected function canBeCut(): bool
577  {
578  return !$this->isRecordInClipboard('cut')
579  && $this->canBeEdited()
580  && !$this->isRecordATranslation();
581  }
582 
588  protected function canBePastedAfter(): bool
589  {
590  $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
591 
592  return $clipboardElementCount
593  && $this->backendUser->check('tables_modify', $this->table)
595  }
596 
604  protected function hasDisableColumnWithValue(int $value): bool
605  {
606  if (isset($GLOBALS['TCA'][$this->table]['ctrl']['enablecolumns']['disabled'])) {
607  $hiddenFieldName = $GLOBALS['TCA'][$this->table]['ctrl']['enablecolumns']['disabled'];
608  if (
609  $hiddenFieldName !== '' && !empty($GLOBALS['TCA'][$this->table]['columns'][$hiddenFieldName]['exclude'])
610  && $this->backendUser->check('non_exclude_fields', $this->table . ':' . $hiddenFieldName)
611  ) {
612  return (int)$this->record[$hiddenFieldName] === (int)$value;
613  }
614  }
615  return false;
616  }
617 
623  protected function isRecordLocked(): bool
624  {
625  return (int)$this->pageRecord['editlock'] === 1
626  || isset($GLOBALS['TCA'][$this->table]['ctrl']['editlock'])
627  && (int)$this->record[$GLOBALS['TCA'][$this->table]['ctrl']['editlock']] === 1;
628  }
629 
635  protected function isDeletePlaceholder(): bool
636  {
637  if (!isset($this->record['t3ver_state'])) {
638  return false;
639  }
640  return VersionState::cast($this->record['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER);
641  }
642 
649  protected function isRecordInClipboard(string $mode = ''): bool
650  {
651  $isSelected = '';
652  if ($this->clipboard->current === 'normal') {
653  $isSelected = $this->clipboard->isSelected($this->table, $this->record['uid']);
654  }
655  return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
656  }
657 
663  protected function isRecordATranslation(): bool
664  {
665  return BackendUtility::isTableLocalizable($this->table) && (int)$this->record[$GLOBALS['TCA'][$this->table]['ctrl']['transOrigPointerField']] !== 0;
666  }
667 
671  protected function getIdentifier(): string
672  {
673  return $this->record['uid'];
674  }
675 }
static translationCount($table, $ref, $msg='')
static getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static referenceCount($table, $ref, $msg='', $count=null)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static fixed_lgd_cs($string, $chars, $appendString='...')
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']