‪TYPO3CMS  11.5
DataHandlerHook.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Doctrine\DBAL\Exception as DBALException;
19 use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform;
20 use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
33 use ‪TYPO3\CMS\Core\SysLog\Action\Database as DatabaseAction;
34 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
43 
50 {
57  protected ‪$notificationEmailInfo = [];
58 
64  protected ‪$remappedIds = [];
65 
66  /****************************
67  ***** Cmdmap Hooks ******
68  ****************************/
74  public function ‪processCmdmap_beforeStart(‪DataHandler $dataHandler)
75  {
76  // Reset notification array
77  $this->notificationEmailInfo = [];
78  // Resolve dependencies of version/workspaces actions:
79  $dataHandler->cmdmap = $this->‪getCommandMap($dataHandler)->‪process()->‪get();
80  }
81 
92  public function ‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, ‪DataHandler $dataHandler)
93  {
94  // custom command "version"
95  if ($command !== 'version') {
96  return;
97  }
98  $commandIsProcessed = true;
99  $action = (string)$value['action'];
100  $comment = $value['comment'] ?? '';
101  $notificationAlternativeRecipients = $value['notificationAlternativeRecipients'] ?? [];
102  switch ($action) {
103  case 'new':
104  $dataHandler->‪versionizeRecord($table, $id, $value['label']);
105  break;
106  case 'swap':
107  case 'publish':
108  $this->‪version_swap(
109  $table,
110  $id,
111  $value['swapWith'],
112  $dataHandler,
113  $comment,
114  $notificationAlternativeRecipients
115  );
116  break;
117  case 'clearWSID':
118  case 'flush':
119  $dataHandler->‪discard($table, (int)$id);
120  break;
121  case 'setStage':
122  $elementIds = ‪GeneralUtility::intExplode(',', (string)$id, true);
123  foreach ($elementIds as $elementId) {
124  $this->‪version_setStage(
125  $table,
126  $elementId,
127  $value['stageId'],
128  $comment,
129  $dataHandler,
130  $notificationAlternativeRecipients
131  );
132  }
133  break;
134  default:
135  // Do nothing
136  }
137  }
138 
145  public function ‪processCmdmap_afterFinish(‪DataHandler $dataHandler)
146  {
147  // Empty accumulation array
148  $emailNotificationService = GeneralUtility::makeInstance(StageChangeNotification::class);
150  $this->notificationEmailInfo,
151  $emailNotificationService,
152  $dataHandler
153  );
154 
155  // Reset notification array
156  $this->notificationEmailInfo = [];
157  // Reset remapped IDs
158  $this->remappedIds = [];
159 
160  $this->‪flushWorkspaceCacheEntriesByWorkspaceId((int)$dataHandler->BE_USER->workspace);
161  }
162 
163  protected function ‪sendStageChangeNotification(
164  array $accumulatedNotificationInformation,
165  ‪StageChangeNotification $notificationService,
166  ‪DataHandler $dataHandler
167  ): void {
168  foreach ($accumulatedNotificationInformation as $groupedNotificationInformation) {
169  $emails = (array)$groupedNotificationInformation['recipients'];
170  if (empty($emails)) {
171  continue;
172  }
173  $workspaceRec = $groupedNotificationInformation['shared'][0];
174  if (!is_array($workspaceRec)) {
175  continue;
176  }
177  $notificationService->‪notifyStageChange(
178  $workspaceRec,
179  (int)$groupedNotificationInformation['shared'][1],
180  $groupedNotificationInformation['elements'],
181  $groupedNotificationInformation['shared'][2],
182  $emails,
183  $dataHandler->BE_USER
184  );
185 
186  if ($dataHandler->enableLogging) {
187  [$elementTable, $elementUid] = reset($groupedNotificationInformation['elements']);
188  $propertyArray = $dataHandler->‪getRecordProperties($elementTable, $elementUid);
189  $pid = $propertyArray['pid'];
190  $dataHandler->‪log($elementTable, $elementUid, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::MESSAGE, 'Notification email for stage change was sent to "{recipients}"', -1, ['recipients' => implode('", "', array_column($emails, 'email'))], $dataHandler->‪eventPid($elementTable, $elementUid, $pid));
191  }
192  }
193  }
194 
204  public function ‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
205  {
206  // only process the hook if it wasn't processed
207  // by someone else before
208  if ($recordWasDeleted) {
209  return;
210  }
211  $recordWasDeleted = true;
212  // For Live version, try if there is a workspace version because if so, rather "delete" that instead
213  // Look, if record is an offline version, then delete directly:
214  if ((int)($record['t3ver_oid'] ?? 0) === 0) {
215  if ($wsVersion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $id)) {
216  $record = $wsVersion;
217  $id = $record['uid'];
218  }
219  }
220  $recordVersionState = ‪VersionState::cast($record['t3ver_state'] ?? 0);
221  // Look, if record is an offline version, then delete directly:
222  if ((int)($record['t3ver_oid'] ?? 0) > 0) {
223  if (BackendUtility::isTableWorkspaceEnabled($table)) {
224  // In Live workspace, delete any. In other workspaces there must be match.
225  if ($dataHandler->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $dataHandler->BE_USER->workspace) {
226  $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
227  // Processing can be skipped if a delete placeholder shall be published
228  // during the current request. Thus it will be deleted later on...
229  $liveRecordVersionState = ‪VersionState::cast($liveRec['t3ver_state']);
230  if ($recordVersionState->equals(‪VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
231  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'])
232  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
233  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
234  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
235  ) {
236  return null;
237  }
238 
239  if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(‪VersionState::DEFAULT_STATE)) {
240  // Change normal versioned record to delete placeholder
241  // Happens when an edited record is deleted
242  GeneralUtility::makeInstance(ConnectionPool::class)
243  ->getConnectionForTable($table)
244  ->update(
245  $table,
246  ['t3ver_state' => ‪VersionState::DELETE_PLACEHOLDER],
247  ['uid' => $id]
248  );
249 
250  // Delete localization overlays:
251  $dataHandler->deleteL10nOverlayRecords($table, $id);
252  } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
253  // Delete those in WS 0 + if their live records state was not "Placeholder".
254  $dataHandler->deleteEl($table, $id);
255  } elseif ($recordVersionState->equals(‪VersionState::NEW_PLACEHOLDER)) {
256  $placeholderRecord = BackendUtility::getLiveVersionOfRecord($table, (int)$id);
257  $dataHandler->deleteEl($table, (int)$id);
258  if (is_array($placeholderRecord)) {
259  $this->‪softOrHardDeleteSingleRecord($table, (int)$placeholderRecord['uid']);
260  }
261  }
262  } else {
263  $dataHandler->log($table, (int)$id, DatabaseAction::DELETE, 0, SystemLogErrorClassification::USER_ERROR, 'Tried to delete record from another workspace');
264  }
265  } else {
266  $dataHandler->log($table, (int)$id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::USER_ERROR, 'Versioning not enabled for record with an online ID (t3ver_oid) given');
267  }
268  } elseif ($recordVersionState->equals(‪VersionState::NEW_PLACEHOLDER)) {
269  // If it is a new versioned record, delete it directly.
270  $dataHandler->deleteEl($table, $id);
271  } elseif ($dataHandler->BE_USER->workspaceAllowsLiveEditingInTable($table)) {
272  // Look, if record is "online" then delete directly.
273  $dataHandler->deleteEl($table, $id);
274  } else {
275  // Otherwise, try to delete by versioning:
276  $copyMappingArray = $dataHandler->copyMappingArray;
277  $dataHandler->versionizeRecord($table, $id, 'DELETED!', true);
278  // Determine newly created versions:
279  // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
280  $versionizedElements = ‪ArrayUtility::arrayDiffKeyRecursive($dataHandler->copyMappingArray, $copyMappingArray);
281  // Delete localization overlays:
282  foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
283  foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
284  $dataHandler->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
285  }
286  }
287  }
288  }
289 
301  public function ‪processCmdmap_postProcess($command, $table, $id, $value, DataHandler $dataHandler)
302  {
303  if ($command === 'delete') {
304  if ($table === ‪StagesService::TABLE_STAGE) {
305  $this->‪resetStageOfElements((int)$id);
306  } elseif ($table === ‪WorkspaceService::TABLE_WORKSPACE) {
307  $this->‪flushWorkspaceElements((int)$id);
308  $this->‪emitUpdateTopbarSignal();
309  }
310  }
311  }
312 
313  public function ‪processDatamap_afterAllOperations(‪DataHandler $dataHandler): void
314  {
315  if (isset($dataHandler->datamap[‪WorkspaceService::TABLE_WORKSPACE])) {
316  $this->‪emitUpdateTopbarSignal();
317  }
318  }
319 
333  public function ‪moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
334  {
335  // Only do something in Draft workspace
336  if ($dataHandler->BE_USER->workspace === 0) {
337  return;
338  }
339  $tableSupportsVersioning = BackendUtility::isTableWorkspaceEnabled($table);
340  $recordWasMoved = true;
341  $moveRecVersionState = ‪VersionState::cast((int)($moveRec['t3ver_state'] ?? ‪VersionState::DEFAULT_STATE));
342  // Get workspace version of the source record, if any:
343  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
344  if ($tableSupportsVersioning) {
345  // Create version of record first, if it does not exist
346  if (empty($versionedRecord['uid'])) {
347  $dataHandler->versionizeRecord($table, $uid, 'MovePointer');
348  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
349  if ((int)$resolvedPid !== (int)$propArr['pid']) {
350  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
351  }
352  } elseif ($dataHandler->isRecordCopied($table, $uid) && (int)$dataHandler->copyMappingArray[$table][$uid] === (int)$versionedRecord['uid']) {
353  // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
354  if ((int)$resolvedPid !== (int)$propArr['pid']) {
355  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
356  }
357  }
358  }
359  // Check workspace permissions:
360  $workspaceAccessBlocked = [];
361  // Element was in "New/Deleted/Moved" so it can be moved...
362  $recIsNewVersion = $moveRecVersionState->equals(‪VersionState::NEW_PLACEHOLDER) || $moveRecVersionState->indicatesPlaceholder();
363  $recordMustNotBeVersionized = $dataHandler->BE_USER->workspaceAllowsLiveEditingInTable($table);
364  $canMoveRecord = $recIsNewVersion || $tableSupportsVersioning;
365  // Workspace source check:
366  if (!$recIsNewVersion) {
367  $errorCode = $dataHandler->workspaceCannotEditRecord($table, $versionedRecord['uid'] ?: $uid);
368  if ($errorCode) {
369  $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
370  } elseif (!$canMoveRecord && !$recordMustNotBeVersionized) {
371  $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
372  }
373  }
374  // Workspace destination check:
375  // All records can be inserted if $recordMustNotBeVersionized is true.
376  // Only new versions can be inserted if $recordMustNotBeVersionized is FALSE.
377  if (!($recordMustNotBeVersionized || $canMoveRecord)) {
378  $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
379  }
380 
381  if (empty($workspaceAccessBlocked)) {
382  $versionedRecordUid = (int)$versionedRecord['uid'];
383  // custom moving not needed, just behave like in live workspace (also for newly versioned records)
384  if (!$versionedRecordUid || !$tableSupportsVersioning || $recIsNewVersion) {
385  $recordWasMoved = false;
386  } else {
387  // If the move operation is done on a versioned record, which is
388  // NOT new/deleted placeholder, then mark the versioned record as "moved"
389  $this->‪moveRecord_moveVersionedRecord($table, (int)$uid, (int)$destPid, $versionedRecordUid, $dataHandler);
390  }
391  } else {
392  $dataHandler->log($table, $versionedRecord['uid'] ?: $uid, DatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, 'Move attempt failed due to workspace restrictions: {reason}', -1, ['reason' => implode(' // ', $workspaceAccessBlocked)]);
393  }
394  }
395 
404  protected function ‪moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
405  {
406  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
407  if (empty($versionedRecord)) {
408  return;
409  }
410  foreach ($versionedRecord as $field => $value) {
411  if (empty(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
412  continue;
413  }
415  $dataHandler,
416  $resolvedPageId,
417  $table,
418  $uid,
419  $value,
420  ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']
421  );
422  }
423  }
424 
435  protected function ‪moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $value, array $configuration): void
436  {
437  $inlineFieldType = $dataHandler->getInlineFieldType($configuration);
438  $inlineProcessing = (
439  ($inlineFieldType === 'list' || $inlineFieldType === 'field')
440  && BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])
441  && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
442  );
443 
444  if ($inlineProcessing) {
445  if ($table === 'pages') {
446  // If the inline elements are related to a page record,
447  // make sure they reside at that page and not at its parent
448  $resolvedPageId = $uid;
449  }
450 
451  $dbAnalysis = $this->‪createRelationHandlerInstance();
452  $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
453 
454  // Moving records to a positive destination will insert each
455  // record at the beginning, thus the order is reversed here:
456  foreach ($dbAnalysis->itemArray as $item) {
457  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
458  if (empty($versionedRecord)) {
459  continue;
460  }
461  $versionState = ‪VersionState::cast($versionedRecord['t3ver_state']);
462  if ($versionState->indicatesPlaceholder()) {
463  continue;
464  }
465  $dataHandler->moveRecord($item['table'], $item['id'], $resolvedPageId);
466  }
467  }
468  }
469 
470  /****************************
471  ***** Stage Changes ******
472  ****************************/
483  protected function ‪version_setStage($table, $id, $stageId, string $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
484  {
485  $record = BackendUtility::getRecord($table, $id);
486  if (!is_array($record)) {
487  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to set stage for record failed: No Record');
488  } elseif ($errorCode = $dataHandler->workspaceCannotEditOfflineVersion($table, $record)) {
489  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to set stage for record failed: {reason}', -1, ['reason' => $errorCode]);
490  } elseif ($dataHandler->checkRecordUpdateAccess($table, $id)) {
491  $workspaceInfo = $dataHandler->BE_USER->checkWorkspace($record['t3ver_wsid']);
492  $workspaceId = (int)$workspaceInfo['uid'];
493  $currentStage = (int)$record['t3ver_stage'];
494  // check if the user is allowed to the current stage, so it's also allowed to send to next stage
495  if ($dataHandler->BE_USER->workspaceCheckStageForCurrent($currentStage)) {
496  // Set stage of record:
497  GeneralUtility::makeInstance(ConnectionPool::class)
498  ->getConnectionForTable($table)
499  ->update(
500  $table,
501  [
502  't3ver_stage' => $stageId,
503  ],
504  ['uid' => (int)$id]
505  );
506 
507  if ($dataHandler->enableLogging) {
508  $propertyArray = $dataHandler->getRecordProperties($table, $id);
509  $pid = $propertyArray['pid'];
510  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::MESSAGE, 'Stage for record was changed to {stage}. Comment was: "{comment}"', -1, ['stage' => $stageId, 'comment' => mb_substr($comment, 0, 100)], $dataHandler->eventPid($table, $id, $pid));
511  }
512  // Write the stage change to history
513  $historyStore = $this->‪getRecordHistoryStore($workspaceId, $dataHandler->BE_USER);
514  $historyStore->changeStageForRecord($table, (int)$id, ['current' => $currentStage, 'next' => $stageId, 'comment' => $comment]);
515  if ((int)$workspaceInfo['stagechg_notification'] > 0) {
516  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['shared'] = [$workspaceInfo, $stageId, $comment];
517  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = [$table, $id];
518  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['recipients'] = $notificationAlternativeRecipients;
519  }
520  } else {
521  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::USER_ERROR, 'The member user tried to set a stage value "{stage}" that was not allowed', -1, ['stage' => $stageId]);
522  }
523  } else {
524  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to set stage for record failed because you do not have edit access');
525  }
526  }
527 
528  /*****************************
529  ***** CMD versioning ******
530  *****************************/
531 
543  protected function ‪version_swap($table, $id, $swapWith, DataHandler $dataHandler, string $comment, $notificationAlternativeRecipients = [])
544  {
545  // Check prerequisites before start publishing
546  // Skip records that have been deleted during the current execution
547  if ($dataHandler->hasDeletedRecord($table, $id)) {
548  return;
549  }
550 
551  // First, check if we may actually edit the online record
552  if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
553  $dataHandler->log(
554  $table,
555  $id,
556  DatabaseAction::PUBLISH,
557  0,
558  SystemLogErrorClassification::USER_ERROR,
559  'Error: You cannot swap versions for record %s:%d you do not have access to edit!',
560  -1,
561  [$table, $id]
562  );
563  return;
564  }
565  // Select the two versions:
566  // Currently live version, contents will be removed.
567  $curVersion = BackendUtility::getRecord($table, $id, '*');
568  // Versioned records which contents will be moved into $curVersion
569  $isNewRecord = ((int)($curVersion['t3ver_state'] ?? 0) === ‪VersionState::NEW_PLACEHOLDER);
570  if ($isNewRecord && is_array($curVersion)) {
571  // @todo: This early return is odd. It means version_swap_processFields() and versionPublishManyToManyRelations()
572  // below are not called for new records to be published. This is "fine" for mm since mm tables have no
573  // t3ver_wsid and need no publish as such. For inline relation publishing, this is indirectly resolved by the
574  // processCmdmap_beforeStart() hook, which adds additional commands for child records - a construct we
575  // may want to avoid altogether due to its complexity. It would be easier to follow if publish here would
576  // handle that instead.
577  $this->‪publishNewRecord($table, $curVersion, $dataHandler, $comment, (array)$notificationAlternativeRecipients);
578  return;
579  }
580  $swapVersion = BackendUtility::getRecord($table, $swapWith, '*');
581  if (!(is_array($curVersion) && is_array($swapVersion))) {
582  $dataHandler->log(
583  $table,
584  $id,
585  DatabaseAction::PUBLISH,
586  0,
587  SystemLogErrorClassification::SYSTEM_ERROR,
588  'Error: Either online or swap version for %s:%d->%d could not be selected!',
589  -1,
590  [$table, $id, $swapWith]
591  );
592  return;
593  }
594  $workspaceId = (int)$swapVersion['t3ver_wsid'];
595  $currentStage = (int)$swapVersion['t3ver_stage'];
596  if (!$dataHandler->BE_USER->workspacePublishAccess($workspaceId)) {
597  $dataHandler->log($table, (int)$id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'User could not publish records from workspace #{workspace}', -1, ['workspace' => $workspaceId]);
598  return;
599  }
600  $wsAccess = $dataHandler->BE_USER->checkWorkspace($workspaceId);
601  if (!($workspaceId <= 0 || !($wsAccess['publish_access'] & 1) || $currentStage === ‪StagesService::STAGE_PUBLISH_ID)) {
602  $dataHandler->log($table, (int)$id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'Records in workspace #{workspace} can only be published when in "Publish" stage.', -1, ['workspace' => $workspaceId]);
603  return;
604  }
605  if (!($dataHandler->doesRecordExist($table, $swapWith, ‪Permission::PAGE_SHOW) && $dataHandler->checkRecordUpdateAccess($table, $swapWith))) {
606  $dataHandler->log($table, $swapWith, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'You cannot publish a record you do not have edit and show permissions for');
607  return;
608  }
609  // Check if the swapWith record really IS a version of the original!
610  if (!(((int)$swapVersion['t3ver_oid'] > 0 && (int)$curVersion['t3ver_oid'] === 0) && (int)$swapVersion['t3ver_oid'] === (int)$id)) {
611  $dataHandler->log($table, $swapWith, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'In offline record, either t3ver_oid was not set or the t3ver_oid didn\'t match the id of the online version as it must!');
612  return;
613  }
614  $versionState = new VersionState($swapVersion['t3ver_state']);
615 
616  // Find fields to keep
617  $keepFields = $this->‪getUniqueFields($table);
618  // Sorting needs to be exchanged for moved records
619  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['sortby']) && !$versionState->equals(‪VersionState::MOVE_POINTER)) {
620  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'];
621  }
622  // l10n-fields must be kept otherwise the localization
623  // will be lost during the publishing
624  if (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? false) {
625  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
626  }
627  // Swap "keepfields"
628  foreach ($keepFields as $fN) {
629  $tmp = $swapVersion[$fN];
630  $swapVersion[$fN] = $curVersion[$fN];
631  $curVersion[$fN] = $tmp;
632  }
633  // Preserve states:
634  $t3ver_state = [];
635  $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
636  // Modify offline version to become online:
637  // Set pid for ONLINE (but not for moved records)
638  if (!$versionState->equals(‪VersionState::MOVE_POINTER)) {
639  $swapVersion['pid'] = (int)$curVersion['pid'];
640  }
641  // We clear this because t3ver_oid only make sense for offline versions
642  // and we want to prevent unintentional misuse of this
643  // value for online records.
644  $swapVersion['t3ver_oid'] = 0;
645  // In case of swapping and the offline record has a state
646  // (like 2 or 4 for deleting or move-pointer) we set the
647  // current workspace ID so the record is not deselected.
648  // @todo: It is odd these information are updated in $swapVersion *before* version_swap_processFields
649  // version_swap_processFields() and versionPublishManyToManyRelations() are called. This leads
650  // to the situation that versionPublishManyToManyRelations() needs another argument to transfer
651  // the "from workspace" information which would usually be retrieved by accessing $swapVersion['t3ver_wsid']
652  $swapVersion['t3ver_wsid'] = 0;
653  $swapVersion['t3ver_stage'] = 0;
654  $swapVersion['t3ver_state'] = (string)new VersionState(‪VersionState::DEFAULT_STATE);
655  // Take care of relations in each field (e.g. IRRE):
656  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
657  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
658  if (isset($fieldConf['config']) && is_array($fieldConf['config'])) {
659  $this->‪version_swap_processFields($table, $fieldConf['config'], $curVersion, $swapVersion, $dataHandler);
660  }
661  }
662  }
663  $dataHandler->versionPublishManyToManyRelations($table, $curVersion, $swapVersion, $workspaceId);
664  unset($swapVersion['uid']);
665  // Modify online version to become offline:
666  unset($curVersion['uid']);
667  // Mark curVersion to contain the oid
668  $curVersion['t3ver_oid'] = (int)$id;
669  $curVersion['t3ver_wsid'] = 0;
670  // Increment lifecycle counter
671  $curVersion['t3ver_stage'] = 0;
672  $curVersion['t3ver_state'] = (string)new VersionState(‪VersionState::DEFAULT_STATE);
673  // Generating proper history data to prepare logging
674  $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
675  $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
676 
677  // Execute swapping:
678  $sqlErrors = [];
679  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
680 
681  $platform = $connection->getDatabasePlatform();
682  $tableDetails = null;
683  if ($platform instanceof SQLServerPlatform || $platform instanceof PostgreSQLPlatform) {
684  // mssql and postgres needs to set proper PARAM_LOB and others to update fields.
685  $tableDetails = $connection->createSchemaManager()->listTableDetails($table);
686  }
687 
688  try {
689  $types = [];
690 
691  if ($platform instanceof SQLServerPlatform || $platform instanceof PostgreSQLPlatform) {
692  foreach ($curVersion as $columnName => $columnValue) {
693  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
694  }
695  }
696 
697  $connection->update(
698  $table,
699  $swapVersion,
700  ['uid' => (int)$id],
701  $types
702  );
703  } catch (DBALException $e) {
704  $sqlErrors[] = $e->getPrevious()->getMessage();
705  }
706 
707  if (empty($sqlErrors)) {
708  try {
709  $types = [];
710  if ($platform instanceof SQLServerPlatform || $platform instanceof PostgreSQLPlatform) {
711  foreach ($curVersion as $columnName => $columnValue) {
712  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
713  }
714  }
715 
716  $connection->update(
717  $table,
718  $curVersion,
719  ['uid' => (int)$swapWith],
720  $types
721  );
722  } catch (DBALException $e) {
723  $sqlErrors[] = $e->getPrevious()->getMessage();
724  }
725  }
726 
727  if (!empty($sqlErrors)) {
728  $dataHandler->log($table, $swapWith, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'During Swapping: SQL errors happened: {reason}', -1, ['reason' => implode('; ', $sqlErrors)]);
729  } else {
730  // Update localized elements to use the live l10n_parent now
731  $this->‪updateL10nOverlayRecordsOnPublish($table, $id, $swapWith, $workspaceId, $dataHandler);
732  // Register swapped ids for later remapping:
733  $this->remappedIds[$table][$id] = $swapWith;
734  $this->remappedIds[$table][$swapWith] = $id;
735  if ((int)$t3ver_state['swapVersion'] === ‪VersionState::DELETE_PLACEHOLDER) {
736  // We're publishing a delete placeholder t3ver_state = 2. This means the live record should
737  // be set to deleted. We're currently in some workspace and deal with a live record here. Thus,
738  // we temporarily set backend user workspace to 0 so all operations happen as in live.
739  $currentUserWorkspace = $dataHandler->BE_USER->workspace;
740  $dataHandler->BE_USER->workspace = 0;
741  $dataHandler->deleteEl($table, $id, true);
742  $dataHandler->BE_USER->workspace = $currentUserWorkspace;
743  }
744  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::MESSAGE, 'Publishing successful for table "{table}" uid {liveId}=>{versionId}', -1, ['table' => $table, 'versionId' => $swapWith, 'liveId' => $id], $dataHandler->eventPid($table, $id, $swapVersion['pid']));
745 
746  // Set log entry for live record:
747  $propArr = $dataHandler->getRecordPropertiesFromRow($table, $swapVersion);
748  if (($propArr['t3ver_oid'] ?? 0) > 0) {
749  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
750  } else {
751  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
752  }
753  $dataHandler->log($table, $id, DatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
754  $dataHandler->setHistory($table, $id);
755  // Set log entry for offline record:
756  $propArr = $dataHandler->getRecordPropertiesFromRow($table, $curVersion);
757  if (($propArr['t3ver_oid'] ?? 0) > 0) {
758  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
759  } else {
760  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
761  }
762  $dataHandler->log($table, $swapWith, DatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, $label, 10, [$propArr['header'], $table . ':' . $swapWith], $propArr['event_pid']);
763  $dataHandler->setHistory($table, $swapWith);
764 
766  $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
767  $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
768  $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = [$table, $id];
769  $this->notificationEmailInfo[$notificationEmailInfoKey]['recipients'] = $notificationAlternativeRecipients;
770  // Write to log with stageId -20 (STAGE_PUBLISH_EXECUTE_ID)
771  if ($dataHandler->enableLogging) {
772  $propArr = $dataHandler->getRecordProperties($table, $id);
773  $pid = $propArr['pid'];
774  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::MESSAGE, 'Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', -1, [], $dataHandler->eventPid($table, $id, $pid));
775  }
776  // Write the stage change to the history
777  $historyStore = $this->‪getRecordHistoryStore((int)$wsAccess['uid'], $dataHandler->BE_USER);
778  $historyStore->changeStageForRecord($table, (int)$id, ['current' => $currentStage, 'next' => ‪StagesService::STAGE_PUBLISH_EXECUTE_ID, 'comment' => $comment]);
779 
780  // Clear cache:
781  $dataHandler->registerRecordIdForPageCacheClearing($table, $id);
782  // If published, delete the record from the database
783  if ($table === 'pages') {
784  // Note on fifth argument false: At this point both $curVersion and $swapVersion page records are
785  // identical in DB. deleteEl() would now usually find all records assigned to our obsolete
786  // page which at the same time belong to our current version page, and would delete them.
787  // To suppress this, false tells deleteEl() to only delete the obsolete page but not its assigned records.
788  $dataHandler->deleteEl($table, $swapWith, true, true, false);
789  } else {
790  $dataHandler->deleteEl($table, $swapWith, true, true);
791  }
792 
793  // Update reference index of the live record - which could have been a workspace record in case 'new'
794  $dataHandler->updateRefIndex($table, $id, 0);
795  // The 'swapWith' record has been deleted, so we can drop any reference index the record is involved in
796  $dataHandler->registerReferenceIndexRowsForDrop($table, $swapWith, (int)$dataHandler->BE_USER->workspace);
797  }
798  }
799 
814  protected function ‪updateL10nOverlayRecordsOnPublish(string $table, int $liveId, int $previouslyUsedVersionId, int $workspaceId, DataHandler $dataHandler): void
815  {
816  if (!BackendUtility::isTableLocalizable($table)) {
817  return;
818  }
819  if (!BackendUtility::isTableWorkspaceEnabled($table)) {
820  return;
821  }
822  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
823  $queryBuilder = $connection->createQueryBuilder();
824  $queryBuilder->getRestrictions()->removeAll();
825 
826  $l10nParentFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
827  $constraints = $queryBuilder->expr()->eq(
828  $l10nParentFieldName,
829  $queryBuilder->createNamedParameter($previouslyUsedVersionId, ‪Connection::PARAM_INT)
830  );
831  $translationSourceFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null;
832  if ($translationSourceFieldName) {
833  $constraints = $queryBuilder->expr()->orX(
834  $constraints,
835  $queryBuilder->expr()->eq(
836  $translationSourceFieldName,
837  $queryBuilder->createNamedParameter($previouslyUsedVersionId, ‪Connection::PARAM_INT)
838  )
839  );
840  }
841 
842  $queryBuilder
843  ->select('uid', $l10nParentFieldName)
844  ->from($table)
845  ->where(
846  $constraints,
847  $queryBuilder->expr()->eq(
848  't3ver_wsid',
849  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
850  )
851  );
852 
853  if ($translationSourceFieldName) {
854  $queryBuilder->addSelect($translationSourceFieldName);
855  }
856 
857  $statement = $queryBuilder->executeQuery();
858  while ($record = $statement->fetchAssociative()) {
859  $updateFields = [];
860  $dataTypes = [‪Connection::PARAM_INT];
861  if ((int)$record[$l10nParentFieldName] === $previouslyUsedVersionId) {
862  $updateFields[$l10nParentFieldName] = $liveId;
863  $dataTypes[] = ‪Connection::PARAM_INT;
864  }
865  if ($translationSourceFieldName && (int)$record[$translationSourceFieldName] === $previouslyUsedVersionId) {
866  $updateFields[$translationSourceFieldName] = $liveId;
867  $dataTypes[] = ‪Connection::PARAM_INT;
868  }
869 
870  if (empty($updateFields)) {
871  continue;
872  }
873 
874  $connection->update(
875  $table,
876  $updateFields,
877  ['uid' => (int)$record['uid']],
878  $dataTypes
879  );
880  $dataHandler->updateRefIndex($table, $record['uid']);
881  }
882  }
883 
894  protected function ‪version_swap_processFields($tableName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
895  {
896  $inlineType = $dataHandler->getInlineFieldType($configuration);
897  if ($inlineType !== 'field') {
898  return;
899  }
900  $foreignTable = $configuration['foreign_table'];
901  // Read relations that point to the current record (e.g. live record):
902  $liveRelations = $this->‪createRelationHandlerInstance();
903  $liveRelations->setWorkspaceId(0);
904  $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
905  // Read relations that point to the record to be swapped with e.g. draft record):
906  $versionRelations = $this->‪createRelationHandlerInstance();
907  $versionRelations->setUseLiveReferenceIds(false);
908  $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
909  // Update relations for both (workspace/versioning) sites:
910  if (!empty($liveRelations->itemArray)) {
911  $dataHandler->addRemapAction(
912  $tableName,
913  $liveData['uid'],
914  [$this, 'updateInlineForeignFieldSorting'],
915  [$liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace]
916  );
917  }
918  if (!empty($versionRelations->itemArray)) {
919  $dataHandler->addRemapAction(
920  $tableName,
921  $liveData['uid'],
922  [$this, 'updateInlineForeignFieldSorting'],
923  [$liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0]
924  );
925  }
926  }
927 
938  protected function ‪publishNewRecord(string $table, array $newRecordInWorkspace, DataHandler $dataHandler, string $comment, array $notificationAlternativeRecipients): void
939  {
940  $id = (int)$newRecordInWorkspace['uid'];
941  $workspaceId = (int)$newRecordInWorkspace['t3ver_wsid'];
942  if (!$dataHandler->BE_USER->workspacePublishAccess($workspaceId)) {
943  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'User could not publish records from workspace #{workspace}', -1, ['workspace' => $workspaceId]);
944  return;
945  }
946  $wsAccess = $dataHandler->BE_USER->checkWorkspace($workspaceId);
947  if (!($workspaceId <= 0 || !($wsAccess['publish_access'] & 1) || (int)$newRecordInWorkspace['t3ver_stage'] === ‪StagesService::STAGE_PUBLISH_ID)) {
948  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'Records in workspace #{workspace} can only be published when in "Publish" stage.', -1, ['workspace' => $workspaceId]);
949  return;
950  }
951  if (!($dataHandler->doesRecordExist($table, $id, ‪Permission::PAGE_SHOW) && $dataHandler->checkRecordUpdateAccess($table, $id))) {
952  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::USER_ERROR, 'You cannot publish a record you do not have edit and show permissions for');
953  return;
954  }
955 
956  // Modify versioned record to become online
957  $updatedFields = [
958  't3ver_oid' => 0,
959  't3ver_wsid' => 0,
960  't3ver_stage' => 0,
961  't3ver_state' => ‪VersionState::DEFAULT_STATE,
962  ];
963 
964  try {
965  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
966  $connection->update(
967  $table,
968  $updatedFields,
969  [
970  'uid' => (int)$id,
971  ],
972  [
978  ]
979  );
980  } catch (DBALException $e) {
981  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'During Publishing: SQL errors happened: {reason}', -1, ['reason' => $e->getPrevious()->getMessage()]);
982  }
983 
984  if ($dataHandler->enableLogging) {
985  $dataHandler->log($table, $id, DatabaseAction::PUBLISH, 0, SystemLogErrorClassification::MESSAGE, 'Publishing successful for table "{table}" uid {uid} (new record)', -1, ['table' => $table, 'uid' => $id], $dataHandler->eventPid($table, $id, $newRecordInWorkspace['pid']));
986  }
987 
988  // Set log entry for record
989  $propArr = $dataHandler->getRecordPropertiesFromRow($table, $newRecordInWorkspace);
990  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
991  $dataHandler->log($table, $id, DatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
992  $dataHandler->setHistory($table, $id);
993 
995  $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
996  $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
997  $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = [$table, $id];
998  $this->notificationEmailInfo[$notificationEmailInfoKey]['recipients'] = $notificationAlternativeRecipients;
999  // Write to log with stageId -20 (STAGE_PUBLISH_EXECUTE_ID)
1000  $dataHandler->log($table, $id, DatabaseAction::VERSIONIZE, 0, SystemLogErrorClassification::MESSAGE, 'Stage for record was changed to {stage}. Comment was: "{comment}"', -1, ['stage' => $stageId, 'comment' => substr($comment, 0, 100)], $dataHandler->eventPid($table, $id, $newRecordInWorkspace['pid']));
1001  // Write the stage change to the history (usually this is done in updateDB in DataHandler, but we do a manual SQL change)
1002  $historyStore = $this->‪getRecordHistoryStore((int)$wsAccess['uid'], $dataHandler->BE_USER);
1003  $historyStore->changeStageForRecord($table, $id, ['current' => (int)$newRecordInWorkspace['t3ver_stage'], 'next' => ‪StagesService::STAGE_PUBLISH_EXECUTE_ID, 'comment' => $comment]);
1004 
1005  // Clear cache
1006  $dataHandler->registerRecordIdForPageCacheClearing($table, $id);
1007  // Update the reference index: Drop the references in the workspace, but update them in the live workspace
1008  $dataHandler->registerReferenceIndexRowsForDrop($table, $id, $workspaceId);
1009  $dataHandler->updateRefIndex($table, $id, 0);
1010  $this->‪updateReferenceIndexForL10nOverlays($table, $id, $workspaceId, $dataHandler);
1011 
1012  // When dealing with mm relations on local side, existing refindex rows of the new workspace record
1013  // need to be re-calculated for the now live record. Scenario ManyToMany Publish createContentAndAddRelation
1014  // These calls are similar to what is done in DH->versionPublishManyToManyRelations() and can not be
1015  // used from there since publishing new records does not call that method, see @todo in version_swap().
1016  $dataHandler->registerReferenceIndexUpdateForReferencesToItem($table, $id, $workspaceId, 0);
1017  $dataHandler->registerReferenceIndexUpdateForReferencesToItem($table, $id, $workspaceId);
1018  }
1019 
1029  protected function ‪updateReferenceIndexForL10nOverlays(string $table, int $newVersionedRecordId, int $workspaceId, DataHandler $dataHandler): void
1030  {
1031  if (!BackendUtility::isTableLocalizable($table)) {
1032  return;
1033  }
1034  if (!BackendUtility::isTableWorkspaceEnabled($table)) {
1035  return;
1036  }
1037  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
1038  $queryBuilder = $connection->createQueryBuilder();
1039  $queryBuilder->getRestrictions()->removeAll();
1040 
1041  $l10nParentFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1042  $constraints = $queryBuilder->expr()->eq(
1043  $l10nParentFieldName,
1044  $queryBuilder->createNamedParameter($newVersionedRecordId, ‪Connection::PARAM_INT)
1045  );
1046  $translationSourceFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null;
1047  if ($translationSourceFieldName) {
1048  $constraints = $queryBuilder->expr()->orX(
1049  $constraints,
1050  $queryBuilder->expr()->eq(
1051  $translationSourceFieldName,
1052  $queryBuilder->createNamedParameter($newVersionedRecordId, ‪Connection::PARAM_INT)
1053  )
1054  );
1055  }
1056 
1057  $queryBuilder
1058  ->select('uid', $l10nParentFieldName)
1059  ->from($table)
1060  ->where(
1061  $constraints,
1062  $queryBuilder->expr()->eq(
1063  't3ver_wsid',
1064  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
1065  )
1066  );
1067 
1068  if ($translationSourceFieldName) {
1069  $queryBuilder->addSelect($translationSourceFieldName);
1070  }
1071 
1072  $statement = $queryBuilder->executeQuery();
1073  while ($record = $statement->fetchAssociative()) {
1074  $dataHandler->updateRefIndex($table, $record['uid']);
1075  }
1076  }
1077 
1094  public function ‪updateInlineForeignFieldSorting($parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
1095  {
1096  ‪$remappedIds = [];
1097  // Use remapped ids (live id <-> version id)
1098  foreach ($foreignIds as $foreignId) {
1099  if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
1100  ‪$remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
1101  } else {
1102  ‪$remappedIds[] = $foreignId;
1103  }
1104  }
1105 
1106  $relationHandler = $this->‪createRelationHandlerInstance();
1107  $relationHandler->setWorkspaceId($targetWorkspaceId);
1108  $relationHandler->setUseLiveReferenceIds(false);
1109  $relationHandler->start(implode(',', ‪$remappedIds), $foreignTableName);
1110  $relationHandler->processDeletePlaceholder();
1111  $relationHandler->writeForeignField($configuration, $parentId);
1112  }
1113 
1121  protected function ‪resetStageOfElements(int $stageId): void
1122  {
1123  foreach ($this->‪getTcaTables() as $tcaTable) {
1124  if (BackendUtility::isTableWorkspaceEnabled($tcaTable)) {
1125  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1126  ->getQueryBuilderForTable($tcaTable);
1127 
1128  $queryBuilder
1129  ->update($tcaTable)
1130  ->set('t3ver_stage', ‪StagesService::STAGE_EDIT_ID)
1131  ->where(
1132  $queryBuilder->expr()->eq(
1133  't3ver_stage',
1134  $queryBuilder->createNamedParameter($stageId, ‪Connection::PARAM_INT)
1135  ),
1136  $queryBuilder->expr()->gt(
1137  't3ver_wsid',
1138  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1139  )
1140  )
1141  ->executeStatement();
1142  }
1143  }
1144  }
1145 
1152  protected function ‪flushWorkspaceElements(int $workspaceId): void
1153  {
1154  $command = [];
1155  foreach ($this->‪getTcaTables() as $tcaTable) {
1156  if (BackendUtility::isTableWorkspaceEnabled($tcaTable)) {
1157  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1158  ->getQueryBuilderForTable($tcaTable);
1159  $queryBuilder->getRestrictions()->removeAll();
1160  $result = $queryBuilder
1161  ->select('uid')
1162  ->from($tcaTable)
1163  ->where(
1164  $queryBuilder->expr()->eq(
1165  't3ver_wsid',
1166  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
1167  ),
1168  // t3ver_oid >= 0 basically omits placeholder records here, those would otherwise
1169  // fail to delete later in DH->discard() and would create "can't do that" log entries.
1170  $queryBuilder->expr()->orX(
1171  $queryBuilder->expr()->gt(
1172  't3ver_oid',
1173  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1174  ),
1175  $queryBuilder->expr()->eq(
1176  't3ver_state',
1177  $queryBuilder->createNamedParameter(‪VersionState::NEW_PLACEHOLDER, ‪Connection::PARAM_INT)
1178  )
1179  )
1180  )
1181  ->orderBy('uid')
1182  ->executeQuery();
1183 
1184  while (($recordId = $result->fetchOne()) !== false) {
1185  $command[$tcaTable][$recordId]['version']['action'] = 'flush';
1186  }
1187  }
1188  }
1189  if (!empty($command)) {
1190  // Execute the command array via DataHandler to flush all records from this workspace.
1191  // Switch to target workspace temporarily, otherwise DH->discard() do not
1192  // operate on correct workspace if fetching additional records.
1193  $backendUser = ‪$GLOBALS['BE_USER'];
1194  $savedWorkspace = $backendUser->workspace;
1195  $backendUser->workspace = $workspaceId;
1196  $context = GeneralUtility::makeInstance(Context::class);
1197  $savedWorkspaceContext = $context->getAspect('workspace');
1198  $context->setAspect('workspace', new WorkspaceAspect($workspaceId));
1199 
1200  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
1201  $dataHandler->start([], $command, $backendUser);
1202  $dataHandler->process_cmdmap();
1203 
1204  $backendUser->workspace = $savedWorkspace;
1205  $context->setAspect('workspace', $savedWorkspaceContext);
1206  }
1207  }
1208 
1214  protected function ‪getTcaTables(): array
1215  {
1216  return array_keys(‪$GLOBALS['TCA']);
1217  }
1218 
1224  protected function ‪flushWorkspaceCacheEntriesByWorkspaceId(int $workspaceId): void
1225  {
1226  $workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
1227  $workspacesCache->flushByTag($workspaceId);
1228  }
1229 
1230  /*******************************
1231  ***** helper functions ******
1232  *******************************/
1233 
1242  public function ‪findPageElementsForVersionSwap($table, $id, $offlineId)
1243  {
1244  $rec = BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
1245  $workspaceId = (int)$rec['t3ver_wsid'];
1246  $elementData = [];
1247  if ($workspaceId === 0) {
1248  return $elementData;
1249  }
1250  // Get page UID for LIVE and workspace
1251  if ($table !== 'pages') {
1252  $rec = BackendUtility::getRecord($table, $id, 'pid');
1253  $pageId = $rec['pid'];
1254  $rec = BackendUtility::getRecord('pages', $pageId);
1255  BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1256  $offlinePageId = $rec['_ORIG_uid'];
1257  } else {
1258  $pageId = $id;
1259  $offlinePageId = $offlineId;
1260  }
1261  // Traversing all tables supporting versioning:
1262  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1263  if (BackendUtility::isTableWorkspaceEnabled($table) && $table !== 'pages') {
1264  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1265  ->getQueryBuilderForTable($table);
1266 
1267  $queryBuilder->getRestrictions()
1268  ->removeAll()
1269  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1270 
1271  $statement = $queryBuilder
1272  ->select('A.uid AS offlineUid', 'B.uid AS uid')
1273  ->from($table, 'A')
1274  ->from($table, 'B')
1275  ->where(
1276  $queryBuilder->expr()->gt(
1277  'A.t3ver_oid',
1278  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1279  ),
1280  $queryBuilder->expr()->eq(
1281  'B.pid',
1282  $queryBuilder->createNamedParameter($pageId, ‪Connection::PARAM_INT)
1283  ),
1284  $queryBuilder->expr()->eq(
1285  'A.t3ver_wsid',
1286  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
1287  ),
1288  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1289  )
1290  ->executeQuery();
1291 
1292  while ($row = $statement->fetchAssociative()) {
1293  $elementData[$table][] = [$row['uid'], $row['offlineUid']];
1294  }
1295  }
1296  }
1297  if ($offlinePageId && $offlinePageId != $pageId) {
1298  $elementData['pages'][] = [$pageId, $offlinePageId];
1299  }
1300 
1301  return $elementData;
1302  }
1303 
1311  public function ‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
1312  {
1313  if ($workspaceId == 0) {
1314  return;
1315  }
1316  // Traversing all tables supporting versioning:
1317  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1318  if (BackendUtility::isTableWorkspaceEnabled($table) && $table !== 'pages') {
1319  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1320  ->getQueryBuilderForTable($table);
1321 
1322  $queryBuilder->getRestrictions()
1323  ->removeAll()
1324  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1325 
1326  $statement = $queryBuilder
1327  ->select('A.uid')
1328  ->from($table, 'A')
1329  ->from($table, 'B')
1330  ->where(
1331  $queryBuilder->expr()->gt(
1332  'A.t3ver_oid',
1333  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1334  ),
1335  $queryBuilder->expr()->in(
1336  'B.pid',
1337  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
1338  ),
1339  $queryBuilder->expr()->eq(
1340  'A.t3ver_wsid',
1341  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
1342  ),
1343  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1344  )
1345  ->groupBy('A.uid')
1346  ->executeQuery();
1347 
1348  while ($row = $statement->fetchAssociative()) {
1349  $elementList[$table][] = $row['uid'];
1350  }
1351  if (is_array($elementList[$table])) {
1352  // Yes, it is possible to get non-unique array even with DISTINCT above!
1353  // It happens because several UIDs are passed in the array already.
1354  $elementList[$table] = array_unique($elementList[$table]);
1355  }
1356  }
1357  }
1358  }
1359 
1369  public function ‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
1370  {
1371  if ($workspaceId == 0) {
1372  return;
1373  }
1374 
1375  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1376  ->getQueryBuilderForTable($table);
1377  $queryBuilder->getRestrictions()
1378  ->removeAll()
1379  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1380 
1381  $statement = $queryBuilder
1382  ->select('B.pid')
1383  ->from($table, 'A')
1384  ->from($table, 'B')
1385  ->where(
1386  $queryBuilder->expr()->gt(
1387  'A.t3ver_oid',
1388  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1389  ),
1390  $queryBuilder->expr()->eq(
1391  'A.t3ver_wsid',
1392  $queryBuilder->createNamedParameter($workspaceId, ‪Connection::PARAM_INT)
1393  ),
1394  $queryBuilder->expr()->in(
1395  'A.uid',
1396  $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
1397  ),
1398  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1399  )
1400  ->groupBy('B.pid')
1401  ->executeQuery();
1402 
1403  while ($row = $statement->fetchAssociative()) {
1404  $pageIdList[] = $row['pid'];
1405  // Find ws version
1406  // Note: cannot use BackendUtility::getRecordWSOL()
1407  // here because it does not accept workspace id!
1408  $rec = BackendUtility::getRecord('pages', $row[0]);
1409  BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1410  if ($rec['_ORIG_uid']) {
1411  $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
1412  }
1413  }
1414  // The line below is necessary even with DISTINCT
1415  // because several elements can be passed by caller
1416  $pageIdList = array_unique($pageIdList);
1417  }
1418 
1424  public function ‪findRealPageIds(array &$idList): void
1425  {
1426  foreach ($idList as $key => $id) {
1427  $rec = BackendUtility::getRecord('pages', $id, 't3ver_oid');
1428  if ($rec['t3ver_oid'] > 0) {
1429  $idList[$key] = $rec['t3ver_oid'];
1430  }
1431  }
1432  }
1433 
1446  protected function ‪moveRecord_moveVersionedRecord(string $table, int $liveUid, int $destPid, int $versionedRecordUid, DataHandler $dataHandler): void
1447  {
1448  // If a record gets moved after a record that already has a versioned record
1449  // then the versioned record needs to be placed after the existing one
1450  $originalRecordDestinationPid = $destPid;
1451  $movedTargetRecordInWorkspace = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, abs($destPid), 'uid');
1452  if (is_array($movedTargetRecordInWorkspace) && $destPid < 0) {
1453  $destPid = -$movedTargetRecordInWorkspace['uid'];
1454  }
1455  $dataHandler->moveRecord_raw($table, $versionedRecordUid, $destPid);
1456 
1457  $versionedRecord = BackendUtility::getRecord($table, $versionedRecordUid, 'uid,t3ver_state');
1458  if (!‪VersionState::cast($versionedRecord['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
1459  // Update the state of this record to a move placeholder. This is allowed if the
1460  // record is a 'changed' (t3ver_state=0) record: Changing a record and moving it
1461  // around later, should switch it from 'changed' to 'moved'. Deleted placeholders
1462  // however are an 'end-state', they should not be switched to a move placeholder.
1463  // Scenario: For a live page that has a localization, the localization is first
1464  // marked as to-delete in workspace, creating a delete placeholder for that
1465  // localization. Later, the page is moved around, moving the localization along
1466  // with the default language record. The localization should then NOT be switched
1467  // from 'to-delete' to 'moved', this would loose the 'to-delete' information.
1468  GeneralUtility::makeInstance(ConnectionPool::class)
1469  ->getConnectionForTable($table)
1470  ->update(
1471  $table,
1472  [
1473  't3ver_state' => (string)new VersionState(‪VersionState::MOVE_POINTER),
1474  ],
1475  [
1476  'uid' => (int)$versionedRecordUid,
1477  ]
1478  );
1479  }
1480 
1481  // Check for the localizations of that element and move them as well
1482  $dataHandler->moveL10nOverlayRecords($table, $liveUid, $destPid, $originalRecordDestinationPid);
1483  }
1484 
1491  public function ‪getCommandMap(DataHandler $dataHandler): CommandMap
1492  {
1493  return GeneralUtility::makeInstance(
1494  CommandMap::class,
1495  $this,
1496  $dataHandler,
1497  $dataHandler->cmdmap,
1498  $dataHandler->BE_USER->workspace
1499  );
1500  }
1501 
1502  protected function ‪emitUpdateTopbarSignal(): void
1503  {
1504  BackendUtility::setUpdateSignal('updateTopbar');
1505  }
1506 
1513  protected function ‪getUniqueFields($table): array
1514  {
1515  $listArr = [];
1516  foreach (‪$GLOBALS['TCA'][$table]['columns'] ?? [] as $field => $configArr) {
1517  if ($configArr['config']['type'] === 'input') {
1518  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $configArr['config']['eval'] ?? '', true);
1519  if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
1520  $listArr[] = $field;
1521  }
1522  }
1523  }
1524  return $listArr;
1525  }
1526 
1532  protected function ‪softOrHardDeleteSingleRecord(string $table, int $uid): void
1533  {
1534  $deleteField = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] ?? null;
1535  if ($deleteField) {
1536  GeneralUtility::makeInstance(ConnectionPool::class)
1537  ->getConnectionForTable($table)
1538  ->update(
1539  $table,
1540  [$deleteField => 1],
1541  ['uid' => $uid],
1543  );
1544  } else {
1545  GeneralUtility::makeInstance(ConnectionPool::class)
1546  ->getConnectionForTable($table)
1547  ->delete(
1548  $table,
1549  ['uid' => $uid]
1550  );
1551  }
1552  }
1553 
1561  protected function ‪getRecordHistoryStore(int $workspaceId, BackendUserAuthentication $user): RecordHistoryStore
1562  {
1563  return GeneralUtility::makeInstance(
1564  RecordHistoryStore::class,
1566  (int)$user->user['uid'],
1567  $user->getOriginalUserIdWhenInSwitchUserMode(),
1568  ‪$GLOBALS['EXEC_TIME'],
1569  $workspaceId
1570  );
1571  }
1572 
1576  protected function ‪createRelationHandlerInstance(): RelationHandler
1577  {
1578  return GeneralUtility::makeInstance(RelationHandler::class);
1579  }
1580 
1584  protected function ‪getLanguageService(): LanguageService
1585  {
1586  return ‪$GLOBALS['LANG'];
1587  }
1588 }
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateRefIndex
‪updateRefIndex($table, $uid, int $workspace=null)
Definition: DataHandler.php:7538
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:86
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getLanguageService
‪LanguageService getLanguageService()
Definition: DataHandlerHook.php:1582
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_moveVersionedRecord
‪moveRecord_moveVersionedRecord(string $table, int $liveUid, int $destPid, int $versionedRecordUid, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1444
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord
‪moveRecord($table, $uid, $destPid)
Definition: DataHandler.php:4028
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\USER_BACKEND
‪const USER_BACKEND
Definition: RecordHistoryStore.php:39
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_deleteAction
‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
Definition: DataHandlerHook.php:202
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Core\Context\WorkspaceAspect
Definition: WorkspaceAspect.php:31
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:53
‪TYPO3\CMS\Core\DataHandling\DataHandler\getInlineFieldType
‪string bool getInlineFieldType($conf)
Definition: DataHandler.php:8458
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerRecordIdForPageCacheClearing
‪registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
Definition: DataHandler.php:8641
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteEl
‪deleteEl($table, $uid, $noRecordCheck=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:4808
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook
Definition: DataHandlerHook.php:50
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\TABLE_WORKSPACE
‪const TABLE_WORKSPACE
Definition: WorkspaceService.php:45
‪TYPO3\CMS\Core\DataHandling\DataHandler\compareFieldArrayWithCurrentAndUnset
‪array compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
Definition: DataHandler.php:8012
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_processFields
‪moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
Definition: DataHandlerHook.php:402
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:37
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_setStage
‪version_setStage($table, $id, $stageId, string $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:481
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\publishNewRecord
‪publishNewRecord(string $table, array $newRecordInWorkspace, DataHandler $dataHandler, string $comment, array $notificationAlternativeRecipients)
Definition: DataHandlerHook.php:936
‪TYPO3\CMS\Core\DataHandling\DataHandler\setHistory
‪setHistory($table, $id)
Definition: DataHandler.php:7502
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processDatamap_afterAllOperations
‪processDatamap_afterAllOperations(DataHandler $dataHandler)
Definition: DataHandlerHook.php:311
‪TYPO3\CMS\Core\DataHandling\DataHandler\discard
‪discard(string $table, ?int $uid, array $record=null)
Definition: DataHandler.php:5531
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceElements
‪flushWorkspaceElements(int $workspaceId)
Definition: DataHandlerHook.php:1150
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap
Definition: CommandMap.php:34
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist
‪bool doesRecordExist($table, $id, int $perms)
Definition: DataHandler.php:6834
‪TYPO3\CMS\Core\DataHandling\DataHandler\workspaceCannotEditOfflineVersion
‪string workspaceCannotEditOfflineVersion(string $table, array $record)
Definition: DataHandler.php:9232
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordCopied
‪bool isRecordCopied($table, $uid)
Definition: DataHandler.php:8613
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerReferenceIndexUpdateForReferencesToItem
‪registerReferenceIndexUpdateForReferencesToItem(string $table, int $uid, int $workspace, int $targetWorkspace=null)
Definition: DataHandler.php:7568
‪TYPO3\CMS\Core\SysLog\Action\Database
Definition: Database.php:24
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$notificationEmailInfo
‪array $notificationEmailInfo
Definition: DataHandlerHook.php:56
‪TYPO3\CMS\Workspaces\Notification\StageChangeNotification\notifyStageChange
‪notifyStageChange(array $workspaceRecord, int $stageId, array $affectedElements, string $comment, array $recipients, BackendUserAuthentication $currentUser)
Definition: StageChangeNotification.php:77
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\updateInlineForeignFieldSorting
‪updateInlineForeignFieldSorting($parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
Definition: DataHandlerHook.php:1092
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:61
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:161
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_EXECUTE_ID
‪const STAGE_PUBLISH_EXECUTE_ID
Definition: StagesService.php:35
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:78
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Workspaces\Notification\StageChangeNotification
Definition: StageChangeNotification.php:44
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap\process
‪CommandMap process()
Definition: CommandMap.php:228
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_beforeStart
‪processCmdmap_beforeStart(DataHandler $dataHandler)
Definition: DataHandlerHook.php:72
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_ID
‪const STAGE_PUBLISH_ID
Definition: StagesService.php:37
‪TYPO3\CMS\Core\DataHandling\DataHandler\workspaceCannotEditRecord
‪string workspaceCannotEditRecord($table, $recData)
Definition: DataHandler.php:9256
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore
Definition: RecordHistoryStore.php:31
‪TYPO3\CMS\Core\DataHandling\DataHandler\hasDeletedRecord
‪bool hasDeletedRecord($tableName, $uid)
Definition: DataHandler.php:9098
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveL10nOverlayRecords
‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
Definition: DataHandler.php:4340
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getUniqueFields
‪array getUniqueFields($table)
Definition: DataHandlerHook.php:1511
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_postProcess
‪processCmdmap_postProcess($command, $table, $id, $value, DataHandler $dataHandler)
Definition: DataHandlerHook.php:299
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap
‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $dataHandler)
Definition: DataHandlerHook.php:90
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication\getOriginalUserIdWhenInSwitchUserMode
‪int null getOriginalUserIdWhenInSwitchUserMode()
Definition: BackendUserAuthentication.php:2304
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\softOrHardDeleteSingleRecord
‪softOrHardDeleteSingleRecord(string $table, int $uid)
Definition: DataHandlerHook.php:1530
‪TYPO3\CMS\Core\Utility\ArrayUtility\arrayDiffKeyRecursive
‪static array arrayDiffKeyRecursive(array $array1, array $array2)
Definition: ArrayUtility.php:768
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap\get
‪array get()
Definition: CommandMap.php:102
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\emitUpdateTopbarSignal
‪emitUpdateTopbarSignal()
Definition: DataHandlerHook.php:1500
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionizeRecord
‪int null versionizeRecord($table, $id, $label, $delete=false)
Definition: DataHandler.php:5889
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_raw
‪moveRecord_raw($table, $uid, $destPid)
Definition: DataHandler.php:4106
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageIdsForVersionStateChange
‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
Definition: DataHandlerHook.php:1367
‪TYPO3\CMS\Core\DataHandling\DataHandler\eventPid
‪int eventPid($table, $uid, $pid)
Definition: DataHandler.php:7245
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getRecordHistoryStore
‪getRecordHistoryStore(int $workspaceId, BackendUserAuthentication $user)
Definition: DataHandlerHook.php:1559
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$remappedIds
‪array $remappedIds
Definition: DataHandlerHook.php:62
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapAction
‪addRemapAction($table, $id, callable $callback, array $arguments)
Definition: DataHandler.php:6572
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteL10nOverlayRecords
‪deleteL10nOverlayRecords($table, $uid)
Definition: DataHandler.php:5315
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordUpdateAccess
‪bool checkRecordUpdateAccess($table, $id, $data=false, $hookObjectsArr=null)
Definition: DataHandler.php:6701
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\resetStageOfElements
‪resetStageOfElements(int $stageId)
Definition: DataHandlerHook.php:1119
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:36
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord
‪moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
Definition: DataHandlerHook.php:331
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_EDIT_ID
‪const STAGE_EDIT_ID
Definition: StagesService.php:38
‪TYPO3\CMS\Workspaces\Service\StagesService
Definition: StagesService.php:32
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceCacheEntriesByWorkspaceId
‪flushWorkspaceCacheEntriesByWorkspaceId(int $workspaceId)
Definition: DataHandlerHook.php:1222
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\createRelationHandlerInstance
‪RelationHandler createRelationHandlerInstance()
Definition: DataHandlerHook.php:1574
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getCommandMap
‪CommandMap getCommandMap(DataHandler $dataHandler)
Definition: DataHandlerHook.php:1489
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_afterFinish
‪processCmdmap_afterFinish(DataHandler $dataHandler)
Definition: DataHandlerHook.php:143
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\sendStageChangeNotification
‪sendStageChangeNotification(array $accumulatedNotificationInformation, StageChangeNotification $notificationService, DataHandler $dataHandler)
Definition: DataHandlerHook.php:161
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:44
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:927
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\updateL10nOverlayRecordsOnPublish
‪updateL10nOverlayRecordsOnPublish(string $table, int $liveId, int $previouslyUsedVersionId, int $workspaceId, DataHandler $dataHandler)
Definition: DataHandlerHook.php:812
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap_processFields
‪version_swap_processFields($tableName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
Definition: DataHandlerHook.php:892
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordProperties
‪array getRecordProperties($table, $id, $noWSOL=false)
Definition: DataHandler.php:7207
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findRealPageIds
‪findRealPageIds(array &$idList)
Definition: DataHandlerHook.php:1422
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionSwap
‪array findPageElementsForVersionSwap($table, $id, $offlineId)
Definition: DataHandlerHook.php:1240
‪TYPO3\CMS\Workspaces\Service\StagesService\TABLE_STAGE
‪const TABLE_STAGE
Definition: StagesService.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerReferenceIndexRowsForDrop
‪registerReferenceIndexRowsForDrop(string $table, int $uid, int $workspace)
Definition: DataHandler.php:7557
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\updateReferenceIndexForL10nOverlays
‪updateReferenceIndexForL10nOverlays(string $table, int $newVersionedRecordId, int $workspaceId, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1027
‪TYPO3\CMS\Core\DataHandling\DataHandler\log
‪int log($table, $recuid, $action, $recpid, $error, $details, $details_nr=-1, $data=[], $event_pid=-1, $NEWid='')
Definition: DataHandler.php:8986
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordPropertiesFromRow
‪array null getRecordPropertiesFromRow($table, $row)
Definition: DataHandler.php:7224
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionStageChange
‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
Definition: DataHandlerHook.php:1309
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionPublishManyToManyRelations
‪versionPublishManyToManyRelations(string $table, array $liveRecord, array $workspaceRecord, int $fromWorkspace)
Definition: DataHandler.php:5970
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getTcaTables
‪array getTcaTables()
Definition: DataHandlerHook.php:1212
‪TYPO3\CMS\Workspaces\Hook
Definition: BackendUtilityHook.php:18
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_processFieldValue
‪moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $value, array $configuration)
Definition: DataHandlerHook.php:433
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap
‪version_swap($table, $id, $swapWith, DataHandler $dataHandler, string $comment, $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:541