‪TYPO3CMS  10.4
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\DBALException;
19 use Doctrine\DBAL\Platforms\SQLServerPlatform;
32 use ‪TYPO3\CMS\Core\SysLog\Action\Database as DatabaseAction;
33 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
42 
49 {
56  protected ‪$notificationEmailInfo = [];
57 
63  protected ‪$remappedIds = [];
64 
65  /****************************
66  ***** Cmdmap Hooks ******
67  ****************************/
73  public function ‪processCmdmap_beforeStart(‪DataHandler $dataHandler)
74  {
75  // Reset notification array
76  $this->notificationEmailInfo = [];
77  // Resolve dependencies of version/workspaces actions:
78  $dataHandler->cmdmap = $this->‪getCommandMap($dataHandler)->‪process()->‪get();
79  }
80 
91  public function ‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, ‪DataHandler $dataHandler)
92  {
93  // custom command "version"
94  if ($command !== 'version') {
95  return;
96  }
97  $commandIsProcessed = true;
98  $action = (string)$value['action'];
99  $comment = $value['comment'] ?: '';
100  $notificationAlternativeRecipients = $value['notificationAlternativeRecipients'] ?? [];
101  switch ($action) {
102  case 'new':
103  $dataHandler->‪versionizeRecord($table, $id, $value['label']);
104  break;
105  case 'swap':
106  $this->‪version_swap(
107  $table,
108  $id,
109  $value['swapWith'],
110  (bool)$value['swapIntoWS'],
111  $dataHandler,
112  $comment,
113  $notificationAlternativeRecipients
114  );
115  break;
116  case 'clearWSID':
117  case 'flush':
118  $dataHandler->‪discard($table, (int)$id);
119  break;
120  case 'setStage':
121  $elementIds = ‪GeneralUtility::intExplode(',', (string)$id, true);
122  foreach ($elementIds as $elementId) {
123  $this->‪version_setStage(
124  $table,
125  $elementId,
126  $value['stageId'],
127  $comment,
128  $dataHandler,
129  $notificationAlternativeRecipients
130  );
131  }
132  break;
133  default:
134  // Do nothing
135  }
136  }
137 
144  public function ‪processCmdmap_afterFinish(‪DataHandler $dataHandler)
145  {
146  // Empty accumulation array
147  $emailNotificationService = GeneralUtility::makeInstance(StageChangeNotification::class);
149  $this->notificationEmailInfo,
150  $emailNotificationService,
151  $dataHandler
152  );
153 
154  // Reset notification array
155  $this->notificationEmailInfo = [];
156  // Reset remapped IDs
157  $this->remappedIds = [];
158 
159  $this->‪flushWorkspaceCacheEntriesByWorkspaceId((int)$dataHandler->BE_USER->workspace);
160  }
161 
162  protected function ‪sendStageChangeNotification(
163  array $accumulatedNotificationInformation,
164  ‪StageChangeNotification $notificationService,
165  ‪DataHandler $dataHandler
166  ): void {
167  foreach ($accumulatedNotificationInformation as $groupedNotificationInformation) {
168  $emails = (array)$groupedNotificationInformation['recipients'];
169  if (empty($emails)) {
170  continue;
171  }
172  $workspaceRec = $groupedNotificationInformation['shared'][0];
173  if (!is_array($workspaceRec)) {
174  continue;
175  }
176  $notificationService->‪notifyStageChange(
177  $workspaceRec,
178  (int)$groupedNotificationInformation['shared'][1],
179  $groupedNotificationInformation['elements'],
180  $groupedNotificationInformation['shared'][2],
181  $emails,
182  $dataHandler->BE_USER
183  );
184 
185  if ($dataHandler->enableLogging) {
186  [$elementTable, $elementUid] = reset($groupedNotificationInformation['elements']);
187  $propertyArray = $dataHandler->‪getRecordProperties($elementTable, $elementUid);
188  $pid = $propertyArray['pid'];
189  $dataHandler->‪log($elementTable, $elementUid, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, 'Notification email for stage change was sent to "' . implode('", "', $emails) . '"', -1, [], $dataHandler->‪eventPid($elementTable, $elementUid, $pid));
190  }
191  }
192  }
193 
203  public function ‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
204  {
205  // only process the hook if it wasn't processed
206  // by someone else before
207  if ($recordWasDeleted) {
208  return;
209  }
210  $recordWasDeleted = true;
211  // For Live version, try if there is a workspace version because if so, rather "delete" that instead
212  // Look, if record is an offline version, then delete directly:
213  if ((int)($record['t3ver_oid'] ?? 0) === 0) {
214  if ($wsVersion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $id)) {
215  $record = $wsVersion;
216  $id = $record['uid'];
217  }
218  }
219  $recordVersionState = ‪VersionState::cast($record['t3ver_state']);
220  // Look, if record is an offline version, then delete directly:
221  if ((int)($record['t3ver_oid'] ?? 0) > 0) {
223  // In Live workspace, delete any. In other workspaces there must be match.
224  if ($dataHandler->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $dataHandler->BE_USER->workspace) {
225  $liveRec = ‪BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
226  // Processing can be skipped if a delete placeholder shall be swapped/published
227  // during the current request. Thus it will be deleted later on...
228  $liveRecordVersionState = ‪VersionState::cast($liveRec['t3ver_state']);
229  if ($recordVersionState->equals(‪VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
230  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'])
231  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
232  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
233  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
234  ) {
235  return null;
236  }
237 
238  if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(‪VersionState::DEFAULT_STATE)) {
239  // Change normal versioned record to delete placeholder
240  // Happens when an edited record is deleted
241  GeneralUtility::makeInstance(ConnectionPool::class)
242  ->getConnectionForTable($table)
243  ->update(
244  $table,
245  ['t3ver_state' => ‪VersionState::DELETE_PLACEHOLDER],
246  ['uid' => $id]
247  );
248 
249  // Delete localization overlays:
250  $dataHandler->deleteL10nOverlayRecords($table, $id);
251  } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
252  // Delete those in WS 0 + if their live records state was not "Placeholder".
253  $dataHandler->deleteEl($table, $id);
254  if ($recordVersionState->equals(‪VersionState::MOVE_POINTER)) {
255  // Delete move-placeholder if current version record is a move-to-pointer.
256  // deleteEl() can't be used here: The deleteEl() for the MOVE_POINTER record above
257  // already triggered a delete cascade for children (inline, ...). If we'd
258  // now call deleteEl() again, we'd trigger adding delete placeholder records for children.
259  // Thus, it's safe here to just set the MOVE_PLACEHOLDER to deleted (or drop row) straight ahead.
260  $movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid', $record['t3ver_wsid']);
261  if (!empty($movePlaceholder)) {
262  $this->‪softOrHardDeleteSingleRecord($table, (int)$movePlaceholder['uid']);
263  }
264  }
265  } elseif ($recordVersionState->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)) {
266  $placeholderRecord = ‪BackendUtility::getLiveVersionOfRecord($table, (int)$id);
267  $dataHandler->deleteEl($table, (int)$id);
268  if (is_array($placeholderRecord)) {
269  $this->‪softOrHardDeleteSingleRecord($table, (int)$placeholderRecord['uid']);
270  }
271  }
272  } else {
273  $dataHandler->newlog('Tried to delete record from another workspace', SystemLogErrorClassification::USER_ERROR);
274  }
275  } else {
276  $dataHandler->newlog('Versioning not enabled for record with an online ID (t3ver_oid) given', SystemLogErrorClassification::SYSTEM_ERROR);
277  }
278  } elseif ($dataHandler->BE_USER->workspaceAllowsLiveEditingInTable($table)) {
279  // Look, if record is "online" then delete directly.
280  $dataHandler->deleteEl($table, $id);
281  } elseif ($recordVersionState->equals(‪VersionState::MOVE_PLACEHOLDER)) {
282  // Placeholders for moving operations are deletable directly.
283  // Get record which its a placeholder for and reset the t3ver_state of that:
284  if ($wsRec = ‪BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
285  // Clear the state flag of the workspace version of the record
286  // Setting placeholder state value for version (so it can know it is currently a new version...)
287 
288  GeneralUtility::makeInstance(ConnectionPool::class)
289  ->getConnectionForTable($table)
290  ->update(
291  $table,
292  [
293  't3ver_state' => (string)new VersionState(‪VersionState::DEFAULT_STATE)
294  ],
295  ['uid' => (int)$wsRec['uid']]
296  );
297  }
298  $dataHandler->deleteEl($table, $id);
299  } else {
300  // Otherwise, try to delete by versioning:
301  $copyMappingArray = $dataHandler->copyMappingArray;
302  $dataHandler->versionizeRecord($table, $id, 'DELETED!', true);
303  // Determine newly created versions:
304  // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
305  $versionizedElements = ‪ArrayUtility::arrayDiffKeyRecursive($dataHandler->copyMappingArray, $copyMappingArray);
306  // Delete localization overlays:
307  foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
308  foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
309  $dataHandler->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
310  }
311  }
312  }
313  }
314 
326  public function ‪processCmdmap_postProcess($command, $table, $id, $value, DataHandler $dataHandler)
327  {
328  if ($command === 'delete') {
329  if ($table === ‪StagesService::TABLE_STAGE) {
330  $this->‪resetStageOfElements((int)$id);
331  } elseif ($table === ‪WorkspaceService::TABLE_WORKSPACE) {
332  $this->‪flushWorkspaceElements((int)$id);
333  $this->‪emitUpdateTopbarSignal();
334  }
335  }
336  }
337 
338  public function ‪processDatamap_afterAllOperations(‪DataHandler $dataHandler): void
339  {
340  if (isset($dataHandler->datamap[‪WorkspaceService::TABLE_WORKSPACE])) {
341  $this->‪emitUpdateTopbarSignal();
342  }
343  }
344 
358  public function ‪moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
359  {
360  // Only do something in Draft workspace
361  if ($dataHandler->BE_USER->workspace === 0) {
362  return;
363  }
364  $tableSupportsVersioning = ‪BackendUtility::isTableWorkspaceEnabled($table);
365  // Fetch move placeholder, since it might point to a new page in the current workspace
366  $movePlaceHolder = ‪BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid,pid');
367  if ($movePlaceHolder !== false && $destPid < 0) {
368  $resolvedPid = $movePlaceHolder['pid'];
369  }
370  $recordWasMoved = true;
371  $moveRecVersionState = ‪VersionState::cast((int)($moveRec['t3ver_state'] ?? ‪VersionState::DEFAULT_STATE));
372  // Get workspace version of the source record, if any:
373  $workspaceVersion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
374  // Handle move-placeholders if the current record is not one already
375  if (
376  $tableSupportsVersioning
377  && !$moveRecVersionState->equals(‪VersionState::MOVE_PLACEHOLDER)
378  ) {
379  // Create version of record first, if it does not exist
380  if (empty($workspaceVersion['uid'])) {
381  $dataHandler->versionizeRecord($table, $uid, 'MovePointer');
382  $workspaceVersion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
383  if ((int)$resolvedPid !== (int)$propArr['pid']) {
384  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
385  }
386  } elseif ($dataHandler->isRecordCopied($table, $uid) && (int)$dataHandler->copyMappingArray[$table][$uid] === (int)$workspaceVersion['uid']) {
387  // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
388  if ((int)$resolvedPid !== (int)$propArr['pid']) {
389  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
390  }
391  }
392  }
393  // Check workspace permissions:
394  $workspaceAccessBlocked = [];
395  // Element was in "New/Deleted/Moved" so it can be moved...
396  $recIsNewVersion = $moveRecVersionState->indicatesPlaceholder();
397  $recordMustNotBeVersionized = $dataHandler->BE_USER->workspaceAllowsLiveEditingInTable($table);
398  $canMoveRecord = $recIsNewVersion || $tableSupportsVersioning;
399  // Workspace source check:
400  if (!$recIsNewVersion) {
401  $errorCode = $dataHandler->BE_USER->workspaceCannotEditRecord($table, $workspaceVersion['uid'] ?: $uid);
402  if ($errorCode) {
403  $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
404  } elseif (!$canMoveRecord && !$recordMustNotBeVersionized) {
405  $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
406  }
407  }
408  // Workspace destination check:
409  // All records can be inserted if $recordMustNotBeVersionized is true.
410  // Only new versions can be inserted if $recordMustNotBeVersionized is FALSE.
411  if (!($recordMustNotBeVersionized || $canMoveRecord && !$recordMustNotBeVersionized)) {
412  $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
413  }
414 
415  if (empty($workspaceAccessBlocked)) {
416  $versionedRecordUid = (int)$workspaceVersion['uid'];
417  // moving not needed, just behave like in live workspace
418  if (!$versionedRecordUid || !$tableSupportsVersioning) {
419  $recordWasMoved = false;
420  } elseif ($recIsNewVersion) {
421  // A newly created record is marked to be moved, so TYPO3 Core is taking care of moving
422  // the new placeholder.
423  $recordWasMoved = false;
424  // However, TYPO3 Core should move the versioned record as well, which is done directly in Core,
425  // before the placeholder is moved.
426  $dataHandler->moveRecord_raw($table, $versionedRecordUid, (int)$destPid);
427  } else {
428  // If the move operation is done on a versioned record, which is
429  // NOT new/deleted placeholder, then also create a move placeholder
430  $this->‪moveRecord_wsPlaceholders($table, (int)$uid, (int)$destPid, (int)$resolvedPid, $versionedRecordUid, $dataHandler);
431  }
432  } else {
433  $dataHandler->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), SystemLogErrorClassification::USER_ERROR);
434  }
435  }
436 
445  protected function ‪moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
446  {
447  $versionedRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
448  if (empty($versionedRecord)) {
449  return;
450  }
451  foreach ($versionedRecord as $field => $value) {
452  if (empty(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
453  continue;
454  }
456  $dataHandler,
457  $resolvedPageId,
458  $table,
459  $uid,
460  $value,
461  ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']
462  );
463  }
464  }
465 
476  protected function ‪moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $value, array $configuration): void
477  {
478  $inlineFieldType = $dataHandler->getInlineFieldType($configuration);
479  $inlineProcessing = (
480  ($inlineFieldType === 'list' || $inlineFieldType === 'field')
481  && ‪BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])
482  && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
483  );
484 
485  if ($inlineProcessing) {
486  if ($table === 'pages') {
487  // If the inline elements are related to a page record,
488  // make sure they reside at that page and not at its parent
489  $resolvedPageId = $uid;
490  }
491 
492  $dbAnalysis = $this->‪createRelationHandlerInstance();
493  $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
494 
495  // Moving records to a positive destination will insert each
496  // record at the beginning, thus the order is reversed here:
497  foreach ($dbAnalysis->itemArray as $item) {
498  $versionedRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
499  if (empty($versionedRecord) || ‪VersionState::cast($versionedRecord['t3ver_state'])->indicatesPlaceholder()) {
500  continue;
501  }
502  $dataHandler->moveRecord($item['table'], $item['id'], $resolvedPageId);
503  }
504  }
505  }
506 
507  /****************************
508  ***** Stage Changes ******
509  ****************************/
520  protected function ‪version_setStage($table, $id, $stageId, string $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
521  {
522  if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
523  $dataHandler->newlog('Attempt to set stage for record failed: ' . $errorCode, SystemLogErrorClassification::USER_ERROR);
524  } elseif ($dataHandler->checkRecordUpdateAccess($table, $id)) {
525  $record = ‪BackendUtility::getRecord($table, $id);
526  $workspaceInfo = $dataHandler->BE_USER->checkWorkspace($record['t3ver_wsid']);
527  // check if the user is allowed to the current stage, so it's also allowed to send to next stage
528  if ($dataHandler->BE_USER->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
529  // Set stage of record:
530  GeneralUtility::makeInstance(ConnectionPool::class)
531  ->getConnectionForTable($table)
532  ->update(
533  $table,
534  [
535  't3ver_stage' => $stageId,
536  ],
537  ['uid' => (int)$id]
538  );
539 
540  if ($dataHandler->enableLogging) {
541  $propertyArray = $dataHandler->getRecordProperties($table, $id);
542  $pid = $propertyArray['pid'];
543  $dataHandler->log($table, $id, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, 'Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', -1, [], $dataHandler->eventPid($table, $id, $pid));
544  }
545  // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
546  $dataHandler->log($table, $id, DatabaseAction::UPDATE, 0, SystemLogErrorClassification::MESSAGE, 'Stage raised...', 30, ['comment' => $comment, 'stage' => $stageId]);
547  if ((int)$workspaceInfo['stagechg_notification'] > 0) {
548  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['shared'] = [$workspaceInfo, $stageId, $comment];
549  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = [$table, $id];
550  $this->notificationEmailInfo[$workspaceInfo['uid'] . ':' . $stageId . ':' . $comment]['recipients'] = $notificationAlternativeRecipients;
551  }
552  } else {
553  $dataHandler->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', SystemLogErrorClassification::USER_ERROR);
554  }
555  } else {
556  $dataHandler->newlog('Attempt to set stage for record failed because you do not have edit access', SystemLogErrorClassification::USER_ERROR);
557  }
558  }
559 
560  /*****************************
561  ***** CMD versioning ******
562  *****************************/
563 
576  protected function ‪version_swap($table, $id, $swapWith, bool $swapIntoWS, DataHandler $dataHandler, string $comment, $notificationAlternativeRecipients = [])
577  {
578  // Check prerequisites before start swapping
579 
580  // Skip records that have been deleted during the current execution
581  if ($dataHandler->hasDeletedRecord($table, $id)) {
582  return;
583  }
584 
585  // First, check if we may actually edit the online record
586  if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
587  $dataHandler->newlog(
588  sprintf(
589  'Error: You cannot swap versions for record %s:%d you do not have access to edit!',
590  $table,
591  $id
592  ),
593  SystemLogErrorClassification::USER_ERROR
594  );
595  return;
596  }
597  // Select the two versions:
598  $curVersion = ‪BackendUtility::getRecord($table, $id, '*');
599  $swapVersion = ‪BackendUtility::getRecord($table, $swapWith, '*');
600  $movePlh = [];
601  $movePlhID = 0;
602  if (!(is_array($curVersion) && is_array($swapVersion))) {
603  $dataHandler->newlog(
604  sprintf(
605  'Error: Either online or swap version for %s:%d->%d could not be selected!',
606  $table,
607  $id,
608  $swapWith
609  ),
610  SystemLogErrorClassification::SYSTEM_ERROR
611  );
612  return;
613  }
614  $workspaceId = (int)$swapVersion['t3ver_wsid'];
615  if (!$dataHandler->BE_USER->workspacePublishAccess($workspaceId)) {
616  $dataHandler->newlog('User could not publish records from workspace #' . $workspaceId, SystemLogErrorClassification::USER_ERROR);
617  return;
618  }
619  $wsAccess = $dataHandler->BE_USER->checkWorkspace($workspaceId);
620  if (!($workspaceId <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10)) {
621  $dataHandler->newlog('Records in workspace #' . $workspaceId . ' can only be published when in "Publish" stage.', SystemLogErrorClassification::USER_ERROR);
622  return;
623  }
624  if (!($dataHandler->doesRecordExist($table, $swapWith, ‪Permission::PAGE_SHOW) && $dataHandler->checkRecordUpdateAccess($table, $swapWith))) {
625  $dataHandler->newlog('You cannot publish a record you do not have edit and show permissions for', SystemLogErrorClassification::USER_ERROR);
626  return;
627  }
628  if ($swapIntoWS && !$dataHandler->BE_USER->workspaceSwapAccess()) {
629  $dataHandler->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', SystemLogErrorClassification::USER_ERROR);
630  return;
631  }
632  // Check if the swapWith record really IS a version of the original!
633  if (!(((int)$swapVersion['t3ver_oid'] > 0 && (int)$curVersion['t3ver_oid'] === 0) && (int)$swapVersion['t3ver_oid'] === (int)$id)) {
634  $dataHandler->newlog('In swap version, either t3ver_oid was not set or the t3ver_oid didn\'t match the id of the online version as it must!', SystemLogErrorClassification::SYSTEM_ERROR);
635  return;
636  }
637 
638  // Find fields to keep
639  $keepFields = $this->‪getUniqueFields($table);
640  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby']) {
641  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'];
642  }
643  // l10n-fields must be kept otherwise the localization
644  // will be lost during the publishing
645  if (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
646  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
647  }
648  // Swap "keepfields"
649  foreach ($keepFields as $fN) {
650  $tmp = $swapVersion[$fN];
651  $swapVersion[$fN] = $curVersion[$fN];
652  $curVersion[$fN] = $tmp;
653  }
654  // Preserve states:
655  $t3ver_state = [];
656  $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
657  // Modify offline version to become online:
658  $tmp_wsid = $swapVersion['t3ver_wsid'];
659  // Set pid for ONLINE
660  $swapVersion['pid'] = (int)$curVersion['pid'];
661  // We clear this because t3ver_oid only make sense for offline versions
662  // and we want to prevent unintentional misuse of this
663  // value for online records.
664  $swapVersion['t3ver_oid'] = 0;
665  // In case of swapping and the offline record has a state
666  // (like 2 or 4 for deleting or move-pointer) we set the
667  // current workspace ID so the record is not deselected
668  // in the interface by BackendUtility::versioningPlaceholderClause()
669  $swapVersion['t3ver_wsid'] = 0;
670  if ($swapIntoWS) {
671  if ($t3ver_state['swapVersion'] > 0) {
672  $swapVersion['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
673  } else {
674  $swapVersion['t3ver_wsid'] = (int)$curVersion['t3ver_wsid'];
675  }
676  }
677  $swapVersion['t3ver_tstamp'] = ‪$GLOBALS['EXEC_TIME'];
678  $swapVersion['t3ver_stage'] = 0;
679  if (!$swapIntoWS) {
680  $swapVersion['t3ver_state'] = (string)new VersionState(‪VersionState::DEFAULT_STATE);
681  }
682  // Moving element.
684  // && $t3ver_state['swapVersion']==4 // Maybe we don't need this?
685  if ($plhRec = ‪BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . (‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
686  $movePlhID = $plhRec['uid'];
687  $movePlh['pid'] = $swapVersion['pid'];
688  $swapVersion['pid'] = (int)$plhRec['pid'];
689  $curVersion['t3ver_state'] = (int)$swapVersion['t3ver_state'];
690  $swapVersion['t3ver_state'] = (string)new VersionState(‪VersionState::DEFAULT_STATE);
691  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby']) {
692  // sortby is a "keepFields" which is why this will work...
693  $movePlh[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']];
694  $swapVersion[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']];
695  }
696  }
697  }
698  // Take care of relations in each field (e.g. IRRE):
699  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
700  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
701  if (isset($fieldConf['config']) && is_array($fieldConf['config'])) {
702  $this->‪version_swap_processFields($table, $fieldConf['config'], $curVersion, $swapVersion, $dataHandler);
703  }
704  }
705  }
706  unset($swapVersion['uid']);
707  // Modify online version to become offline:
708  unset($curVersion['uid']);
709  // Mark curVersion to contain the oid
710  $curVersion['t3ver_oid'] = (int)$id;
711  $curVersion['t3ver_wsid'] = $swapIntoWS ? (int)$tmp_wsid : 0;
712  $curVersion['t3ver_tstamp'] = ‪$GLOBALS['EXEC_TIME'];
713  $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
714  // Increment lifecycle counter
715  $curVersion['t3ver_stage'] = 0;
716  if (!$swapIntoWS) {
717  $curVersion['t3ver_state'] = (string)new VersionState(‪VersionState::DEFAULT_STATE);
718  }
719  // Registering and swapping MM relations in current and swap records:
720  $dataHandler->version_remapMMForVersionSwap($table, $id, $swapWith);
721  // Generating proper history data to prepare logging
722  $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
723  $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
724 
725  // Execute swapping:
726  $sqlErrors = [];
727  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
728 
729  $platform = $connection->getDatabasePlatform();
730  $tableDetails = null;
731  if ($platform instanceof SQLServerPlatform) {
732  // mssql needs to set proper PARAM_LOB and others to update fields
733  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
734  }
735 
736  try {
737  $types = [];
738 
739  if ($platform instanceof SQLServerPlatform) {
740  foreach ($curVersion as $columnName => $columnValue) {
741  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
742  }
743  }
744 
745  $connection->update(
746  $table,
747  $swapVersion,
748  ['uid' => (int)$id],
749  $types
750  );
751  } catch (DBALException $e) {
752  $sqlErrors[] = $e->getPrevious()->getMessage();
753  }
754 
755  if (empty($sqlErrors)) {
756  try {
757  $types = [];
758  if ($platform instanceof SQLServerPlatform) {
759  foreach ($curVersion as $columnName => $columnValue) {
760  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
761  }
762  }
763 
764  $connection->update(
765  $table,
766  $curVersion,
767  ['uid' => (int)$swapWith],
768  $types
769  );
770  } catch (DBALException $e) {
771  $sqlErrors[] = $e->getPrevious()->getMessage();
772  }
773  }
774 
775  if (!empty($sqlErrors)) {
776  $dataHandler->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), SystemLogErrorClassification::SYSTEM_ERROR);
777  } else {
778  // Update localized elements to use the live l10n_parent now
779  $this->‪updateL10nOverlayRecordsOnPublish($table, $id, $swapWith, $workspaceId, $dataHandler);
780  // Register swapped ids for later remapping:
781  $this->remappedIds[$table][$id] = $swapWith;
782  $this->remappedIds[$table][$swapWith] = $id;
783  // If a moving operation took place...:
784  if ($movePlhID) {
785  // Remove, if normal publishing:
786  if (!$swapIntoWS) {
787  // For delete + completely delete!
788  $dataHandler->deleteEl($table, $movePlhID, true, true);
789  } else {
790  // Otherwise update the movePlaceholder:
791  GeneralUtility::makeInstance(ConnectionPool::class)
792  ->getConnectionForTable($table)
793  ->update(
794  $table,
795  $movePlh,
796  ['uid' => (int)$movePlhID]
797  );
798  $dataHandler->updateRefIndex($table, $movePlhID);
799  }
800  }
801  // Checking for delete:
802  if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === ‪VersionState::NEW_PLACEHOLDER)) {
803  // Force delete
804  // Delete t3ver_state = 1 record as t3ver_state = -1 record is going to be live
805  $dataHandler->deleteEl($table, $id, true);
806  }
807  if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === ‪VersionState::DELETE_PLACEHOLDER)) {
808  // We're publishing a delete placeholder t3ver_state = 2. This means the live record should
809  // be set to deleted. We're currently in some workspace and deal with a live record here. Thus,
810  // we temporarily set backend user workspace to 0 so all operations happen as in live.
811  $currentUserWorkspace = $dataHandler->BE_USER->workspace;
812  $dataHandler->BE_USER->workspace = 0;
813  $dataHandler->deleteEl($table, $id, true);
814  $dataHandler->BE_USER->workspace = $currentUserWorkspace;
815  }
816  if ($dataHandler->enableLogging) {
817  $dataHandler->log($table, $id, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, ($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, -1, [], $dataHandler->eventPid($table, $id, $swapVersion['pid']));
818  }
819 
820  // Set log entry for live record:
821  $propArr = $dataHandler->getRecordPropertiesFromRow($table, $swapVersion);
822  if (($propArr['t3ver_oid'] ?? 0) > 0) {
823  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
824  } else {
825  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
826  }
827  $theLogId = $dataHandler->log($table, $id, DatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
828  $dataHandler->setHistory($table, $id, $theLogId);
829  // Set log entry for offline record:
830  $propArr = $dataHandler->getRecordPropertiesFromRow($table, $curVersion);
831  if (($propArr['t3ver_oid'] ?? 0) > 0) {
832  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
833  } else {
834  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
835  }
836  $theLogId = $dataHandler->log($table, $swapWith, DatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, $label, 10, [$propArr['header'], $table . ':' . $swapWith], $propArr['event_pid']);
837  $dataHandler->setHistory($table, $swapWith, $theLogId);
838 
840  $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
841  $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
842  $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = [$table, $id];
843  $this->notificationEmailInfo[$notificationEmailInfoKey]['recipients'] = $notificationAlternativeRecipients;
844  // Write to log with stageId -20 (STAGE_PUBLISH_EXECUTE_ID)
845  if ($dataHandler->enableLogging) {
846  $propArr = $dataHandler->getRecordProperties($table, $id);
847  $pid = $propArr['pid'];
848  $dataHandler->log($table, $id, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, 'Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', -1, [], $dataHandler->eventPid($table, $id, $pid));
849  }
850  $dataHandler->log($table, $id, DatabaseAction::UPDATE, 0, SystemLogErrorClassification::MESSAGE, 'Published', 30, ['comment' => $comment, 'stage' => $stageId]);
851 
852  // Clear cache:
853  $dataHandler->registerRecordIdForPageCacheClearing($table, $id);
854  // If not swapped, delete the record from the database
855  if (!$swapIntoWS) {
856  if ($table === 'pages') {
857  // Note on fifth argument false: At this point both $curVersion and $swapVersion page records are
858  // identical in DB. deleteEl() would now usually find all records assigned to our obsolete
859  // page which at the same time belong to our current version page, and would delete them.
860  // To suppress this, false tells deleteEl() to only delete the obsolete page but not its assigned records.
861  $dataHandler->deleteEl($table, $swapWith, true, true, false);
862  } else {
863  $dataHandler->deleteEl($table, $swapWith, true, true);
864  }
865  }
866 
867  // Update reference index of the live record - which could have been a workspace record in case 'new'
868  $dataHandler->updateRefIndex($table, $id, 0);
869  // The 'swapWith' record has been deleted, so we can drop any reference index the record is involved in
870  $dataHandler->registerReferenceIndexRowsForDrop($table, $swapWith, (int)$dataHandler->BE_USER->workspace);
871  }
872  }
873 
888  protected function ‪updateL10nOverlayRecordsOnPublish(string $table, int $liveId, int $previouslyUsedVersionId, int $workspaceId, DataHandler $dataHandler): void
889  {
891  return;
892  }
894  return;
895  }
896  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
897  $queryBuilder = $connection->createQueryBuilder();
898  $queryBuilder->getRestrictions()->removeAll();
899 
900  $l10nParentFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
901  $constraints = $queryBuilder->expr()->eq(
902  $l10nParentFieldName,
903  $queryBuilder->createNamedParameter($previouslyUsedVersionId, \PDO::PARAM_INT)
904  );
905  $translationSourceFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null;
906  if ($translationSourceFieldName) {
907  $constraints = $queryBuilder->expr()->orX(
908  $constraints,
909  $queryBuilder->expr()->eq(
910  $translationSourceFieldName,
911  $queryBuilder->createNamedParameter($previouslyUsedVersionId, \PDO::PARAM_INT)
912  )
913  );
914  }
915 
916  $queryBuilder
917  ->select('uid', $l10nParentFieldName)
918  ->from($table)
919  ->where(
920  $constraints,
921  $queryBuilder->expr()->eq(
922  't3ver_wsid',
923  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
924  )
925  );
926 
927  if ($translationSourceFieldName) {
928  $queryBuilder->addSelect($translationSourceFieldName);
929  }
930 
931  $statement = $queryBuilder->execute();
932  while ($record = $statement->fetch()) {
933  $updateFields = [];
934  $dataTypes = [\PDO::PARAM_INT];
935  if ((int)$record[$l10nParentFieldName] === $previouslyUsedVersionId) {
936  $updateFields[$l10nParentFieldName] = $liveId;
937  $dataTypes[] = \PDO::PARAM_INT;
938  }
939  if ($translationSourceFieldName && (int)$record[$translationSourceFieldName] === $previouslyUsedVersionId) {
940  $updateFields[$translationSourceFieldName] = $liveId;
941  $dataTypes[] = \PDO::PARAM_INT;
942  }
943 
944  if (empty($updateFields)) {
945  continue;
946  }
947 
948  $connection->update(
949  $table,
950  $updateFields,
951  ['uid' => (int)$record['uid']],
952  $dataTypes
953  );
954  $dataHandler->updateRefIndex($table, $record['uid']);
955  }
956  }
957 
968  protected function ‪version_swap_processFields($tableName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
969  {
970  $inlineType = $dataHandler->getInlineFieldType($configuration);
971  if ($inlineType !== 'field') {
972  return;
973  }
974  $foreignTable = $configuration['foreign_table'];
975  // Read relations that point to the current record (e.g. live record):
976  $liveRelations = $this->‪createRelationHandlerInstance();
977  $liveRelations->setWorkspaceId(0);
978  $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
979  // Read relations that point to the record to be swapped with e.g. draft record):
980  $versionRelations = $this->‪createRelationHandlerInstance();
981  $versionRelations->setUseLiveReferenceIds(false);
982  $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
983  // Update relations for both (workspace/versioning) sites:
984  if (!empty($liveRelations->itemArray)) {
985  $dataHandler->addRemapAction(
986  $tableName,
987  $liveData['uid'],
988  [$this, 'updateInlineForeignFieldSorting'],
989  [$liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace]
990  );
991  }
992  if (!empty($versionRelations->itemArray)) {
993  $dataHandler->addRemapAction(
994  $tableName,
995  $liveData['uid'],
996  [$this, 'updateInlineForeignFieldSorting'],
997  [$liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0]
998  );
999  }
1000  }
1001 
1018  public function ‪updateInlineForeignFieldSorting($parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
1019  {
1020  ‪$remappedIds = [];
1021  // Use remapped ids (live id <-> version id)
1022  foreach ($foreignIds as $foreignId) {
1023  if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
1024  ‪$remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
1025  } else {
1026  ‪$remappedIds[] = $foreignId;
1027  }
1028  }
1029 
1030  $relationHandler = $this->‪createRelationHandlerInstance();
1031  $relationHandler->setWorkspaceId($targetWorkspaceId);
1032  $relationHandler->setUseLiveReferenceIds(false);
1033  $relationHandler->start(implode(',', ‪$remappedIds), $foreignTableName);
1034  $relationHandler->processDeletePlaceholder();
1035  $relationHandler->writeForeignField($configuration, $parentId);
1036  }
1037 
1045  protected function ‪resetStageOfElements(int $stageId): void
1046  {
1047  foreach ($this->‪getTcaTables() as $tcaTable) {
1049  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1050  ->getQueryBuilderForTable($tcaTable);
1051 
1052  $queryBuilder
1053  ->update($tcaTable)
1054  ->set('t3ver_stage', ‪StagesService::STAGE_EDIT_ID)
1055  ->where(
1056  $queryBuilder->expr()->eq(
1057  't3ver_stage',
1058  $queryBuilder->createNamedParameter($stageId, \PDO::PARAM_INT)
1059  ),
1060  $queryBuilder->expr()->gt(
1061  't3ver_wsid',
1062  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1063  )
1064  )
1065  ->execute();
1066  }
1067  }
1068  }
1069 
1076  protected function ‪flushWorkspaceElements(int $workspaceId): void
1077  {
1078  $command = [];
1079  foreach ($this->‪getTcaTables() as $tcaTable) {
1081  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1082  ->getQueryBuilderForTable($tcaTable);
1083  $queryBuilder->getRestrictions()->removeAll();
1084  $result = $queryBuilder
1085  ->select('uid')
1086  ->from($tcaTable)
1087  ->where(
1088  $queryBuilder->expr()->eq(
1089  't3ver_wsid',
1090  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1091  ),
1092  // t3ver_oid >= 0 basically omits placeholder records here, those would otherwise
1093  // fail to delete later in DH->discard() and would create "can't do that" log entries.
1094  $queryBuilder->expr()->gt(
1095  't3ver_oid',
1096  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1097  )
1098  )
1099  ->orderBy('uid')
1100  ->execute();
1101 
1102  while (($recordId = $result->fetchColumn()) !== false) {
1103  $command[$tcaTable][$recordId]['version']['action'] = 'flush';
1104  }
1105  }
1106  }
1107  if (!empty($command)) {
1108  // Execute the command array via DataHandler to flush all records from this workspace.
1109  // Switch to target workspace temporarily, otherwise DH->discard() do not
1110  // operate on correct workspace if fetching additional records.
1111  $backendUser = ‪$GLOBALS['BE_USER'];
1112  $savedWorkspace = $backendUser->workspace;
1113  $backendUser->workspace = $workspaceId;
1114  $context = GeneralUtility::makeInstance(Context::class);
1115  $savedWorkspaceContext = $context->getAspect('workspace');
1116  $context->setAspect('workspace', new WorkspaceAspect($workspaceId));
1117 
1118  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
1119  $dataHandler->start([], $command, $backendUser);
1120  $dataHandler->process_cmdmap();
1121 
1122  $backendUser->workspace = $savedWorkspace;
1123  $context->setAspect('workspace', $savedWorkspaceContext);
1124  }
1125  }
1126 
1132  protected function ‪getTcaTables(): array
1133  {
1134  return array_keys(‪$GLOBALS['TCA']);
1135  }
1136 
1142  protected function ‪flushWorkspaceCacheEntriesByWorkspaceId(int $workspaceId): void
1143  {
1144  $workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
1145  $workspacesCache->flushByTag($workspaceId);
1146  }
1147 
1148  /*******************************
1149  ***** helper functions ******
1150  *******************************/
1151 
1160  public function ‪findPageElementsForVersionSwap($table, $id, $offlineId)
1161  {
1162  $rec = ‪BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
1163  $workspaceId = (int)$rec['t3ver_wsid'];
1164  $elementData = [];
1165  if ($workspaceId === 0) {
1166  return $elementData;
1167  }
1168  // Get page UID for LIVE and workspace
1169  if ($table !== 'pages') {
1170  $rec = ‪BackendUtility::getRecord($table, $id, 'pid');
1171  $pageId = $rec['pid'];
1172  $rec = ‪BackendUtility::getRecord('pages', $pageId);
1173  ‪BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1174  $offlinePageId = $rec['_ORIG_uid'];
1175  } else {
1176  $pageId = $id;
1177  $offlinePageId = $offlineId;
1178  }
1179  // Traversing all tables supporting versioning:
1180  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1181  if (‪BackendUtility::isTableWorkspaceEnabled($table) && $table !== 'pages') {
1182  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1183  ->getQueryBuilderForTable($table);
1184 
1185  $queryBuilder->getRestrictions()
1186  ->removeAll()
1187  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1188 
1189  $statement = $queryBuilder
1190  ->select('A.uid AS offlineUid', 'B.uid AS uid')
1191  ->from($table, 'A')
1192  ->from($table, 'B')
1193  ->where(
1194  $queryBuilder->expr()->gt(
1195  'A.t3ver_oid',
1196  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1197  ),
1198  $queryBuilder->expr()->eq(
1199  'B.pid',
1200  $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
1201  ),
1202  $queryBuilder->expr()->eq(
1203  'A.t3ver_wsid',
1204  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1205  ),
1206  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1207  )
1208  ->execute();
1209 
1210  while ($row = $statement->fetch()) {
1211  $elementData[$table][] = [$row['uid'], $row['offlineUid']];
1212  }
1213  }
1214  }
1215  if ($offlinePageId && $offlinePageId != $pageId) {
1216  $elementData['pages'][] = [$pageId, $offlinePageId];
1217  }
1218 
1219  return $elementData;
1220  }
1221 
1229  public function ‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
1230  {
1231  if ($workspaceId == 0) {
1232  return;
1233  }
1234  // Traversing all tables supporting versioning:
1235  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1236  if (‪BackendUtility::isTableWorkspaceEnabled($table) && $table !== 'pages') {
1237  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1238  ->getQueryBuilderForTable($table);
1239 
1240  $queryBuilder->getRestrictions()
1241  ->removeAll()
1242  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1243 
1244  $statement = $queryBuilder
1245  ->select('A.uid')
1246  ->from($table, 'A')
1247  ->from($table, 'B')
1248  ->where(
1249  $queryBuilder->expr()->gt(
1250  'A.t3ver_oid',
1251  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1252  ),
1253  $queryBuilder->expr()->in(
1254  'B.pid',
1255  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
1256  ),
1257  $queryBuilder->expr()->eq(
1258  'A.t3ver_wsid',
1259  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1260  ),
1261  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1262  )
1263  ->groupBy('A.uid')
1264  ->execute();
1265 
1266  while ($row = $statement->fetch()) {
1267  $elementList[$table][] = $row['uid'];
1268  }
1269  if (is_array($elementList[$table])) {
1270  // Yes, it is possible to get non-unique array even with DISTINCT above!
1271  // It happens because several UIDs are passed in the array already.
1272  $elementList[$table] = array_unique($elementList[$table]);
1273  }
1274  }
1275  }
1276  }
1277 
1287  public function ‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
1288  {
1289  if ($workspaceId == 0) {
1290  return;
1291  }
1292 
1293  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1294  ->getQueryBuilderForTable($table);
1295  $queryBuilder->getRestrictions()
1296  ->removeAll()
1297  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1298 
1299  $statement = $queryBuilder
1300  ->select('B.pid')
1301  ->from($table, 'A')
1302  ->from($table, 'B')
1303  ->where(
1304  $queryBuilder->expr()->gt(
1305  'A.t3ver_oid',
1306  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1307  ),
1308  $queryBuilder->expr()->eq(
1309  'A.t3ver_wsid',
1310  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1311  ),
1312  $queryBuilder->expr()->in(
1313  'A.uid',
1314  $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
1315  ),
1316  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1317  )
1318  ->groupBy('B.pid')
1319  ->execute();
1320 
1321  while ($row = $statement->fetch()) {
1322  $pageIdList[] = $row['pid'];
1323  // Find ws version
1324  // Note: cannot use BackendUtility::getRecordWSOL()
1325  // here because it does not accept workspace id!
1326  $rec = ‪BackendUtility::getRecord('pages', $row[0]);
1327  ‪BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1328  if ($rec['_ORIG_uid']) {
1329  $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
1330  }
1331  }
1332  // The line below is necessary even with DISTINCT
1333  // because several elements can be passed by caller
1334  $pageIdList = array_unique($pageIdList);
1335  }
1336 
1342  public function ‪findRealPageIds(array &$idList): void
1343  {
1344  foreach ($idList as $key => $id) {
1345  $rec = ‪BackendUtility::getRecord('pages', $id, 't3ver_oid');
1346  if ($rec['t3ver_oid'] > 0) {
1347  $idList[$key] = $rec['t3ver_oid'];
1348  }
1349  }
1350  }
1351 
1366  protected function ‪moveRecord_wsPlaceholders(string $table, int $uid, int $destPid, int $resolvedId, int $offlineUid, DataHandler $dataHandler): void
1367  {
1368  // If a record gets moved after a record that already has a placeholder record
1369  // then the new placeholder record needs to be after the existing one
1370  $originalRecordDestinationPid = $destPid;
1371  $movePlaceHolder = ‪BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
1372  if ($movePlaceHolder !== false && $destPid < 0) {
1373  $destPid = -$movePlaceHolder['uid'];
1374  }
1375  if ($plh = ‪BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
1376  // The to-be-moved record is a move pointer, so the record has been moved in workspace at least once already.
1377  // First, move the t3ver_state=4 record.
1378  $moveOverlay = ‪BackendUtility::getWorkspaceVersionOfRecord((int)‪$GLOBALS['BE_USER']->workspace, $table, $uid);
1379  if (isset($moveOverlay['uid']) && $moveOverlay['uid'] > 0) {
1380  $dataHandler->moveRecord_raw($table, $moveOverlay['uid'], $destPid);
1381  }
1382  // Then move the t3ver_state=3 record.
1383  $dataHandler->moveRecord_raw($table, $plh['uid'], $destPid);
1384  } else {
1385  // First, we create a placeholder record in the Live workspace that
1386  // represents the position to where the record is eventually moved to.
1387  $newVersion_placeholderFieldArray = [];
1388 
1389  $factory = GeneralUtility::makeInstance(
1390  PlaceholderShadowColumnsResolver::class,
1391  $table,
1392  ‪$GLOBALS['TCA'][$table] ?? []
1393  );
1394  $shadowColumns = $factory->forMovePlaceholder();
1395  // Set values from the versioned record to the move placeholder
1396  if (!empty($shadowColumns)) {
1397  $versionedRecord = ‪BackendUtility::getRecord($table, $offlineUid);
1398  foreach ($shadowColumns as $shadowColumn) {
1399  if (isset($versionedRecord[$shadowColumn])) {
1400  $newVersion_placeholderFieldArray[$shadowColumn] = $versionedRecord[$shadowColumn];
1401  }
1402  }
1403  }
1404 
1405  if (‪$GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1406  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['crdate']] = ‪$GLOBALS['EXEC_TIME'];
1407  }
1408  if (‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1409  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $dataHandler->userid;
1410  }
1411  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1412  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
1413  }
1414  if ($table === 'pages') {
1415  // Copy page access settings from original page to placeholder
1416  $perms_clause = $dataHandler->BE_USER->getPagePermsClause(‪Permission::PAGE_SHOW);
1417  $access = ‪BackendUtility::readPageAccess($uid, $perms_clause);
1418  $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
1419  $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
1420  $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
1421  $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
1422  $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
1423  }
1424  $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
1425  // Setting placeholder state value for temporary record
1426  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(‪VersionState::MOVE_PLACEHOLDER);
1427  // Setting workspace - only so display of place holders can filter out those from other workspaces.
1428  $newVersion_placeholderFieldArray['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
1429  $labelField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1430  if (‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['type'] === 'input') {
1431  $newVersion_placeholderFieldArray[$labelField] = $dataHandler->getPlaceholderTitleForTableLabel($table, 'MOVE-TO PLACEHOLDER for #' . $uid);
1432  }
1433  // moving localized records requires to keep localization-settings for the placeholder too
1434  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['languageField']) && isset(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
1435  $l10nParentRec = ‪BackendUtility::getRecord($table, $uid);
1436  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']];
1437  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1438  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])) {
1439  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']];
1440  }
1441  unset($l10nParentRec);
1442  }
1443  // @todo Check why $destPid cannot be used directly
1444  // Initially, create at root level.
1445  $newVersion_placeholderFieldArray['pid'] = 0;
1446  $id = 'NEW_MOVE_PLH';
1447  // Saving placeholder as 'original'
1448  $dataHandler->insertDB($table, $id, $newVersion_placeholderFieldArray, false);
1449  // Move the new placeholder from temporary root-level to location:
1450  $dataHandler->moveRecord_raw($table, $dataHandler->substNEWwithIDs[$id], $destPid);
1451  // Move the workspace-version of the original to be the version of the move-to-placeholder:
1452  // Setting placeholder state value for version (so it can know it is currently a new version...)
1453  $updateFields = [
1454  'pid' => $resolvedId,
1455  't3ver_state' => (string)new VersionState(‪VersionState::MOVE_POINTER)
1456  ];
1457 
1458  GeneralUtility::makeInstance(ConnectionPool::class)
1459  ->getConnectionForTable($table)
1460  ->update(
1461  $table,
1462  $updateFields,
1463  ['uid' => (int)$offlineUid]
1464  );
1465  }
1466  // Check for the localizations of that element and move them as well
1467  $dataHandler->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
1468  }
1469 
1476  public function ‪getCommandMap(DataHandler $dataHandler): CommandMap
1477  {
1478  return GeneralUtility::makeInstance(
1479  CommandMap::class,
1480  $this,
1481  $dataHandler,
1482  $dataHandler->cmdmap,
1483  $dataHandler->BE_USER->workspace
1484  );
1485  }
1486 
1487  protected function ‪emitUpdateTopbarSignal(): void
1488  {
1489  ‪BackendUtility::setUpdateSignal('updateTopbar');
1490  }
1491 
1498  protected function ‪getUniqueFields($table): array
1499  {
1500  $listArr = [];
1501  foreach (‪$GLOBALS['TCA'][$table]['columns'] ?? [] as $field => $configArr) {
1502  if ($configArr['config']['type'] === 'input') {
1503  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $configArr['config']['eval'], true);
1504  if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
1505  $listArr[] = $field;
1506  }
1507  }
1508  }
1509  return $listArr;
1510  }
1511 
1517  protected function ‪softOrHardDeleteSingleRecord(string $table, int $uid): void
1518  {
1519  $deleteField = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] ?? null;
1520  if ($deleteField) {
1521  GeneralUtility::makeInstance(ConnectionPool::class)
1522  ->getConnectionForTable($table)
1523  ->update(
1524  $table,
1525  [$deleteField => 1],
1526  ['uid' => $uid],
1527  [\PDO::PARAM_INT]
1528  );
1529  } else {
1530  GeneralUtility::makeInstance(ConnectionPool::class)
1531  ->getConnectionForTable($table)
1532  ->delete(
1533  $table,
1534  ['uid' => $uid]
1535  );
1536  }
1537  }
1538 
1542  protected function ‪createRelationHandlerInstance(): RelationHandler
1543  {
1544  return GeneralUtility::makeInstance(RelationHandler::class);
1545  }
1546 
1550  protected function ‪getLanguageService(): LanguageService
1551  {
1552  return ‪$GLOBALS['LANG'];
1553  }
1554 }
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateRefIndex
‪updateRefIndex($table, $uid, int $workspace=null)
Definition: DataHandler.php:7394
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:84
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getLanguageService
‪LanguageService getLanguageService()
Definition: DataHandlerHook.php:1548
‪TYPO3\CMS\Core\DataHandling\DataHandler\setHistory
‪setHistory($table, $id, $logId)
Definition: DataHandler.php:7358
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPlaceholderTitleForTableLabel
‪string getPlaceholderTitleForTableLabel($table, $placeholderContent=null)
Definition: DataHandler.php:1318
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord
‪moveRecord($table, $uid, $destPid)
Definition: DataHandler.php:4038
‪TYPO3\CMS\Core\DataHandling\DataHandler\newlog
‪int newlog($message, $error=SystemLogErrorClassification::MESSAGE)
Definition: DataHandler.php:8850
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_deleteAction
‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
Definition: DataHandlerHook.php:201
‪TYPO3\CMS\Core\Context\WorkspaceAspect
Definition: WorkspaceAspect.php:31
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:47
‪TYPO3\CMS\Core\DataHandling\DataHandler\getInlineFieldType
‪string bool getInlineFieldType($conf)
Definition: DataHandler.php:8293
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerRecordIdForPageCacheClearing
‪registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
Definition: DataHandler.php:8476
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteEl
‪deleteEl($table, $uid, $noRecordCheck=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:4776
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook
Definition: DataHandlerHook.php:49
‪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:7816
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_processFields
‪moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
Definition: DataHandlerHook.php:443
‪TYPO3\CMS\Backend\Utility\BackendUtility\setUpdateSignal
‪static setUpdateSignal($set='', $params='')
Definition: BackendUtility.php:2798
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:35
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_setStage
‪version_setStage($table, $id, $stageId, string $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:518
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processDatamap_afterAllOperations
‪processDatamap_afterAllOperations(DataHandler $dataHandler)
Definition: DataHandlerHook.php:336
‪TYPO3\CMS\Core\DataHandling\DataHandler\discard
‪discard(string $table, ?int $uid, array $record=null)
Definition: DataHandler.php:5400
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceElements
‪flushWorkspaceElements(int $workspaceId)
Definition: DataHandlerHook.php:1074
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap
Definition: CommandMap.php:34
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordCopied
‪bool isRecordCopied($table, $uid)
Definition: DataHandler.php:8448
‪TYPO3\CMS\Core\SysLog\Action\Database
Definition: Database.php:24
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$notificationEmailInfo
‪array $notificationEmailInfo
Definition: DataHandlerHook.php:55
‪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:1016
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:55
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:194
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_EXECUTE_ID
‪const STAGE_PUBLISH_EXECUTE_ID
Definition: StagesService.php:36
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:73
‪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:71
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪TYPO3\CMS\Core\DataHandling\PlaceholderShadowColumnsResolver
Definition: PlaceholderShadowColumnsResolver.php:35
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4021
‪TYPO3\CMS\Core\DataHandling\DataHandler\hasDeletedRecord
‪bool hasDeletedRecord($tableName, $uid)
Definition: DataHandler.php:8949
‪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:4351
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getUniqueFields
‪array getUniqueFields($table)
Definition: DataHandlerHook.php:1496
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_postProcess
‪processCmdmap_postProcess($command, $table, $id, $value, DataHandler $dataHandler)
Definition: DataHandlerHook.php:324
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap
‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $dataHandler)
Definition: DataHandlerHook.php:89
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\softOrHardDeleteSingleRecord
‪softOrHardDeleteSingleRecord(string $table, int $uid)
Definition: DataHandlerHook.php:1515
‪TYPO3\CMS\Core\Utility\ArrayUtility\arrayDiffKeyRecursive
‪static array arrayDiffKeyRecursive(array $array1, array $array2)
Definition: ArrayUtility.php:768
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_wsPlaceholders
‪moveRecord_wsPlaceholders(string $table, int $uid, int $destPid, int $resolvedId, int $offlineUid, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1364
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap\get
‪array get()
Definition: CommandMap.php:102
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionOfRecord
‪static array null getLiveVersionOfRecord($table, $uid, $fields=' *')
Definition: BackendUtility.php:3748
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\emitUpdateTopbarSignal
‪emitUpdateTopbarSignal()
Definition: DataHandlerHook.php:1485
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionizeRecord
‪int null versionizeRecord($table, $id, $label, $delete=false)
Definition: DataHandler.php:5717
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_raw
‪moveRecord_raw($table, $uid, $destPid)
Definition: DataHandler.php:4116
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageIdsForVersionStateChange
‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
Definition: DataHandlerHook.php:1285
‪TYPO3\CMS\Core\DataHandling\DataHandler\eventPid
‪int eventPid($table, $uid, $pid)
Definition: DataHandler.php:7095
‪TYPO3\CMS\Backend\Utility\BackendUtility\getMovePlaceholder
‪static array bool getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
Definition: BackendUtility.php:3851
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$remappedIds
‪array $remappedIds
Definition: DataHandlerHook.php:61
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteL10nOverlayRecords
‪deleteL10nOverlayRecords($table, $uid)
Definition: DataHandler.php:5346
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordUpdateAccess
‪bool checkRecordUpdateAccess($table, $id, $data=false, $hookObjectsArr=null)
Definition: DataHandler.php:6525
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:33
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\resetStageOfElements
‪resetStageOfElements(int $stageId)
Definition: DataHandlerHook.php:1043
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist
‪bool doesRecordExist($table, $id, $perms)
Definition: DataHandler.php:6659
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Backend\Utility\BackendUtility\getWorkspaceVersionOfRecord
‪static array bool getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
Definition: BackendUtility.php:3705
‪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:356
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_EDIT_ID
‪const STAGE_EDIT_ID
Definition: StagesService.php:39
‪TYPO3\CMS\Workspaces\Service\StagesService
Definition: StagesService.php:33
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array false readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:597
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceCacheEntriesByWorkspaceId
‪flushWorkspaceCacheEntriesByWorkspaceId(int $workspaceId)
Definition: DataHandlerHook.php:1140
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap
‪version_remapMMForVersionSwap($table, $id, $swapWith)
Definition: DataHandler.php:5805
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\createRelationHandlerInstance
‪RelationHandler createRelationHandlerInstance()
Definition: DataHandlerHook.php:1540
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getCommandMap
‪CommandMap getCommandMap(DataHandler $dataHandler)
Definition: DataHandlerHook.php:1474
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_afterFinish
‪processCmdmap_afterFinish(DataHandler $dataHandler)
Definition: DataHandlerHook.php:142
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER_VERSION
‪const NEW_PLACEHOLDER_VERSION
Definition: VersionState.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapAction
‪addRemapAction($table, $id, array $callback, array $arguments)
Definition: DataHandler.php:6399
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\sendStageChangeNotification
‪sendStageChangeNotification(array $accumulatedNotificationInformation, StageChangeNotification $notificationService, DataHandler $dataHandler)
Definition: DataHandlerHook.php:160
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:578
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:39
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Utility\BackendUtility\workspaceOL
‪static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
Definition: BackendUtility.php:3586
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap
‪version_swap($table, $id, $swapWith, bool $swapIntoWS, DataHandler $dataHandler, string $comment, $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:574
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:988
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\updateL10nOverlayRecordsOnPublish
‪updateL10nOverlayRecordsOnPublish(string $table, int $liveId, int $previouslyUsedVersionId, int $workspaceId, DataHandler $dataHandler)
Definition: DataHandlerHook.php:886
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap_processFields
‪version_swap_processFields($tableName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
Definition: DataHandlerHook.php:966
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordProperties
‪array getRecordProperties($table, $id, $noWSOL=false)
Definition: DataHandler.php:7055
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findRealPageIds
‪findRealPageIds(array &$idList)
Definition: DataHandlerHook.php:1340
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionSwap
‪array findPageElementsForVersionSwap($table, $id, $offlineId)
Definition: DataHandlerHook.php:1158
‪TYPO3\CMS\Workspaces\Service\StagesService\TABLE_STAGE
‪const TABLE_STAGE
Definition: StagesService.php:34
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerReferenceIndexRowsForDrop
‪registerReferenceIndexRowsForDrop(string $table, int $uid, int $workspace)
Definition: DataHandler.php:7412
‪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:8822
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordPropertiesFromRow
‪array null getRecordPropertiesFromRow($table, $row)
Definition: DataHandler.php:7072
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertDB
‪int null insertDB($table, $id, $fieldArray, $newVersion=false, $suggestedUid=0, $dontSetNewIdIndex=false)
Definition: DataHandler.php:7188
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionStageChange
‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
Definition: DataHandlerHook.php:1227
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getTcaTables
‪array getTcaTables()
Definition: DataHandlerHook.php:1130
‪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:474
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:72