‪TYPO3CMS  9.5
DataHandlerHook.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use Doctrine\DBAL\DBALException;
18 use Doctrine\DBAL\Platforms\SQLServerPlatform;
40 
47 {
55  protected ‪$notificationEmailInfo = [];
56 
62  protected ‪$remappedIds = [];
63 
67  protected ‪$workspaceService;
68 
69  /****************************
70  ***** Cmdmap Hooks ******
71  ****************************/
77  public function ‪processCmdmap_beforeStart(‪DataHandler $dataHandler)
78  {
79  // Reset notification array
80  $this->notificationEmailInfo = [];
81  // Resolve dependencies of version/workspaces actions:
82  $dataHandler->cmdmap = $this->‪getCommandMap($dataHandler)->‪process()->‪get();
83  }
84 
95  public function ‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, ‪DataHandler $dataHandler)
96  {
97  // custom command "version"
98  if ($command === 'version') {
99  $commandIsProcessed = true;
100  $action = (string)$value['action'];
101  $comment = !empty($value['comment']) ? $value['comment'] : '';
102  $notificationAlternativeRecipients = isset($value['notificationAlternativeRecipients']) && is_array($value['notificationAlternativeRecipients']) ? $value['notificationAlternativeRecipients'] : [];
103  switch ($action) {
104  case 'new':
105  $dataHandler->‪versionizeRecord($table, $id, $value['label']);
106  break;
107  case 'swap':
108  $this->‪version_swap(
109  $table,
110  $id,
111  $value['swapWith'],
112  (bool)$value['swapIntoWS'],
113  $dataHandler,
114  $comment,
115  true,
116  $notificationAlternativeRecipients
117  );
118  break;
119  case 'clearWSID':
120  $this->‪version_clearWSID($table, $id, false, $dataHandler);
121  break;
122  case 'flush':
123  $this->‪version_clearWSID($table, $id, true, $dataHandler);
124  break;
125  case 'setStage':
126  $elementIds = GeneralUtility::trimExplode(',', $id, true);
127  foreach ($elementIds as $elementId) {
128  $this->‪version_setStage(
129  $table,
130  $elementId,
131  $value['stageId'],
132  $comment,
133  true,
134  $dataHandler,
135  $notificationAlternativeRecipients
136  );
137  }
138  break;
139  default:
140  // Do nothing
141  }
142  }
143  }
144 
151  public function ‪processCmdmap_afterFinish(‪DataHandler $dataHandler)
152  {
153  // Empty accumulation array:
154  foreach ($this->notificationEmailInfo as $notifItem) {
155  $this->‪notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $dataHandler, $notifItem['alternativeRecipients']);
156  }
157  // Reset notification array
158  $this->notificationEmailInfo = [];
159  // Reset remapped IDs
160  $this->remappedIds = [];
161 
162  $this->‪flushWorkspaceCacheEntriesByWorkspaceId($dataHandler->BE_USER->workspace);
163  }
164 
174  public function ‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, ‪DataHandler $dataHandler)
175  {
176  // only process the hook if it wasn't processed
177  // by someone else before
178  if ($recordWasDeleted) {
179  return;
180  }
181  $recordWasDeleted = true;
182  // For Live version, try if there is a workspace version because if so, rather "delete" that instead
183  // Look, if record is an offline version, then delete directly:
184  if ($record['pid'] != -1) {
185  if ($wsVersion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $id)) {
186  $record = $wsVersion;
187  $id = $record['uid'];
188  }
189  }
190  $recordVersionState = ‪VersionState::cast($record['t3ver_state']);
191  // Look, if record is an offline version, then delete directly:
192  if ($record['pid'] == -1) {
193  if (‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
194  // In Live workspace, delete any. In other workspaces there must be match.
195  if ($dataHandler->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $dataHandler->BE_USER->workspace) {
196  $liveRec = ‪BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
197  // Processing can be skipped if a delete placeholder shall be swapped/published
198  // during the current request. Thus it will be deleted later on...
199  $liveRecordVersionState = ‪VersionState::cast($liveRec['t3ver_state']);
200  if ($recordVersionState->equals(‪VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
201  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'])
202  && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
203  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
204  && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
205  ) {
206  return null;
207  }
208 
209  if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(‪VersionState::DEFAULT_STATE)) {
210  // Change normal versioned record to delete placeholder
211  // Happens when an edited record is deleted
212  GeneralUtility::makeInstance(ConnectionPool::class)
213  ->getConnectionForTable($table)
214  ->update(
215  $table,
216  [
217  't3ver_label' => 'DELETED!',
218  't3ver_state' => ‪VersionState::DELETE_PLACEHOLDER,
219  ],
220  ['uid' => $id]
221  );
222 
223  // Delete localization overlays:
224  $dataHandler->‪deleteL10nOverlayRecords($table, $id);
225  } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
226  // Delete those in WS 0 + if their live records state was not "Placeholder".
227  $dataHandler->‪deleteEl($table, $id);
228  // Delete move-placeholder if current version record is a move-to-pointer
229  if ($recordVersionState->equals(‪VersionState::MOVE_POINTER)) {
230  $movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid', $record['t3ver_wsid']);
231  if (!empty($movePlaceholder)) {
232  $dataHandler->‪deleteEl($table, $movePlaceholder['uid']);
233  }
234  }
235  } else {
236  // If live record was placeholder (new/deleted), rather clear
237  // it from workspace (because it clears both version and placeholder).
238  $this->‪version_clearWSID($table, $id, false, $dataHandler);
239  }
240  } else {
241  $dataHandler->‪newlog('Tried to delete record from another workspace', 1);
242  }
243  } else {
244  $dataHandler->‪newlog('Versioning not enabled for record with PID = -1!', 2);
245  }
246  } elseif ($res = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
247  // Look, if record is "online" or in a versionized branch, then delete directly.
248  if ($res > 0) {
249  $dataHandler->‪deleteEl($table, $id);
250  } else {
251  $dataHandler->‪newlog('Stage of root point did not allow for deletion', 1);
252  }
253  } elseif ($recordVersionState->equals(‪VersionState::MOVE_PLACEHOLDER)) {
254  // Placeholders for moving operations are deletable directly.
255  // Get record which its a placeholder for and reset the t3ver_state of that:
256  if ($wsRec = ‪BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
257  // Clear the state flag of the workspace version of the record
258  // Setting placeholder state value for version (so it can know it is currently a new version...)
259 
260  GeneralUtility::makeInstance(ConnectionPool::class)
261  ->getConnectionForTable($table)
262  ->update(
263  $table,
264  [
265  't3ver_state' => (string)new ‪VersionState(‪VersionState::DEFAULT_STATE)
266  ],
267  ['uid' => (int)$wsRec['uid']]
268  );
269  }
270  $dataHandler->‪deleteEl($table, $id);
271  } else {
272  // Otherwise, try to delete by versioning:
273  $copyMappingArray = $dataHandler->copyMappingArray;
274  $dataHandler->‪versionizeRecord($table, $id, 'DELETED!', true);
275  // Determine newly created versions:
276  // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
277  $versionizedElements = ‪ArrayUtility::arrayDiffAssocRecursive($dataHandler->copyMappingArray, $copyMappingArray);
278  // Delete localization overlays:
279  foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
280  foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
281  $dataHandler->‪deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
282  }
283  }
284  }
285  }
286 
298  public function ‪processCmdmap_postProcess($command, $table, $id, $value, ‪DataHandler $dataHandler)
299  {
300  if ($command === 'delete') {
301  if ($table === ‪StagesService::TABLE_STAGE) {
302  $this->‪resetStageOfElements($id);
303  } elseif ($table === ‪WorkspaceService::TABLE_WORKSPACE) {
304  $this->‪flushWorkspaceElements($id);
305  }
306  }
307  }
308 
322  public function ‪moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, ‪DataHandler $dataHandler)
323  {
324  // Only do something in Draft workspace
325  if ($dataHandler->BE_USER->workspace === 0) {
326  return;
327  }
328  if ($destPid < 0) {
329  // Fetch move placeholder, since it might point to a new page in the current workspace
330  $movePlaceHolder = ‪BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid,pid');
331  if ($movePlaceHolder !== false) {
332  $resolvedPid = $movePlaceHolder['pid'];
333  }
334  }
335  $recordWasMoved = true;
336  $moveRecVersionState = ‪VersionState::cast($moveRec['t3ver_state']);
337  // Get workspace version of the source record, if any:
338  $WSversion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
339  // Handle move-placeholders if the current record is not one already
340  if (
342  && !$moveRecVersionState->equals(‪VersionState::MOVE_PLACEHOLDER)
343  ) {
344  // Create version of record first, if it does not exist
345  if (empty($WSversion['uid'])) {
346  $dataHandler->‪versionizeRecord($table, $uid, 'MovePointer');
347  $WSversion = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
348  if ((int)$resolvedPid !== (int)$propArr['pid']) {
349  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
350  }
351  } elseif ($dataHandler->‪isRecordCopied($table, $uid) && (int)$dataHandler->copyMappingArray[$table][$uid] === (int)$WSversion['uid']) {
352  // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
353  if ((int)$resolvedPid !== (int)$propArr['pid']) {
354  $this->‪moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
355  }
356  }
357  }
358  // Check workspace permissions:
359  $workspaceAccessBlocked = [];
360  // Element was in "New/Deleted/Moved" so it can be moved...
361  $recIsNewVersion = $moveRecVersionState->indicatesPlaceholder();
362  $destRes = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
363  $canMoveRecord = ($recIsNewVersion || ‪BackendUtility::isTableWorkspaceEnabled($table));
364  // Workspace source check:
365  if (!$recIsNewVersion) {
366  $errorCode = $dataHandler->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
367  if ($errorCode) {
368  $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
369  } elseif (!$canMoveRecord && $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
370  $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
371  }
372  }
373  // Workspace destination check:
374  // All records can be inserted if $destRes is greater than zero.
375  // Only new versions can be inserted if $destRes is FALSE.
376  // NO RECORDS can be inserted if $destRes is negative which indicates a stage
377  // not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
378  // since TYPO3 CMS 7, version2 is the default and the only option
379  if (!($destRes > 0 || $canMoveRecord && !$destRes)) {
380  $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
381  } elseif ($destRes == 1 && $WSversion['uid']) {
382  $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
383  }
384  if (empty($workspaceAccessBlocked)) {
385  // If the move operation is done on a versioned record, which is
386  // NOT new/deleted placeholder and versioningWS is in version 2, then...
387  // since TYPO3 CMS 7, version2 is the default and the only option
388  if ($WSversion['uid'] && !$recIsNewVersion && ‪BackendUtility::isTableWorkspaceEnabled($table)) {
389  $this->‪moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $dataHandler);
390  } else {
391  // moving not needed, just behave like in live workspace
392  $recordWasMoved = false;
393  }
394  } else {
395  $dataHandler->‪newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
396  }
397  }
398 
407  protected function ‪moveRecord_processFields(‪DataHandler $dataHandler, $resolvedPageId, $table, $uid)
408  {
409  $versionedRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
410  if (empty($versionedRecord)) {
411  return;
412  }
413  foreach ($versionedRecord as $field => $value) {
414  if (empty(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
415  continue;
416  }
418  $dataHandler,
419  $resolvedPageId,
420  $table,
421  $uid,
422  $field,
423  $value,
424  ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']
425  );
426  }
427  }
428 
440  protected function ‪moveRecord_processFieldValue(‪DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration)
441  {
442  $inlineFieldType = $dataHandler->‪getInlineFieldType($configuration);
443  $inlineProcessing = (
444  ($inlineFieldType === 'list' || $inlineFieldType === 'field')
445  && ‪BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])
446  && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
447  );
448 
449  if ($inlineProcessing) {
450  if ($table === 'pages') {
451  // If the inline elements are related to a page record,
452  // make sure they reside at that page and not at its parent
453  $resolvedPageId = $uid;
454  }
455 
456  $dbAnalysis = $this->‪createRelationHandlerInstance();
457  $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
458 
459  // Moving records to a positive destination will insert each
460  // record at the beginning, thus the order is reversed here:
461  foreach ($dbAnalysis->itemArray as $item) {
462  $versionedRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
463  if (empty($versionedRecord) || ‪VersionState::cast($versionedRecord['t3ver_state'])->indicatesPlaceholder()) {
464  continue;
465  }
466  $dataHandler->‪moveRecord($item['table'], $item['id'], $resolvedPageId);
467  }
468  }
469  }
470 
471  /****************************
472  ***** Notifications ******
473  ****************************/
485  protected function ‪notifyStageChange(array $stat, $stageId, $table, $id, $comment, ‪DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
486  {
487  $workspaceRec = ‪BackendUtility::getRecord('sys_workspace', $stat['uid']);
488  // So, if $id is not set, then $table is taken to be the complete element name!
489  $elementName = $id ? $table . ':' . $id : $table;
490  if (!is_array($workspaceRec)) {
491  return;
492  }
493 
494  // Get the new stage title
495  $stageService = GeneralUtility::makeInstance(StagesService::class);
496  $newStage = $stageService->getStageTitle((int)$stageId);
497  if (empty($notificationAlternativeRecipients)) {
498  // Compile list of recipients:
499  $emails = [];
500  switch ((int)$stat['stagechg_notification']) {
501  case 1:
502  switch ((int)$stageId) {
503  case 1:
504  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['reviewers']);
505  break;
506  case 10:
507  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
508  break;
509  case -1:
510  // List of elements to reject:
511  $allElements = explode(',', $elementName);
512  // Traverse them, and find the history of each
513  foreach ($allElements as $elRef) {
514  [$eTable, $eUid] = explode(':', $elRef);
515 
516  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
517  ->getQueryBuilderForTable('sys_log');
518 
519  $queryBuilder->getRestrictions()->removeAll();
520 
521  $result = $queryBuilder
522  ->select('log_data', 'tstamp', 'userid')
523  ->from('sys_log')
524  ->where(
525  $queryBuilder->expr()->eq(
526  'action',
527  $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT)
528  ),
529  $queryBuilder->expr()->eq(
530  'details_nr',
531  $queryBuilder->createNamedParameter(30, \PDO::PARAM_INT)
532  ),
533  $queryBuilder->expr()->eq(
534  'tablename',
535  $queryBuilder->createNamedParameter($eTable, \PDO::PARAM_STR)
536  ),
537  $queryBuilder->expr()->eq(
538  'recuid',
539  $queryBuilder->createNamedParameter($eUid, \PDO::PARAM_INT)
540  )
541  )
542  ->orderBy('uid', 'DESC')
543  ->execute();
544 
545  // Find all implicated since the last stage-raise from editing to review:
546  while ($dat = $result->fetch()) {
547  $data = unserialize($dat['log_data']);
548  $emails = $this->‪getEmailsForStageChangeNotification($dat['userid'], true) + $emails;
549  if ($data['stage'] == 1) {
550  break;
551  }
552  }
553  }
554  break;
555  case 0:
556  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['members']);
557  break;
558  default:
559  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
560  }
561  break;
562  case 10:
563  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
564  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['reviewers']) + $emails;
565  $emails = $this->‪getEmailsForStageChangeNotification($workspaceRec['members']) + $emails;
566  break;
567  default:
568  // Do nothing
569  }
570  } else {
571  $emails = $notificationAlternativeRecipients;
572  }
573  // prepare and then send the emails
574  if (!empty($emails)) {
575  $previewUriBuilder = GeneralUtility::makeInstance(PreviewUriBuilder::class);
576  // Path to record is found:
577  [$elementTable, $elementUid] = explode(':', $elementName);
578  $elementUid = (int)$elementUid;
579  $elementRecord = ‪BackendUtility::getRecord($elementTable, $elementUid);
580  $recordTitle = ‪BackendUtility::getRecordTitle($elementTable, $elementRecord);
581  if ($elementTable === 'pages') {
582  $pageUid = $elementUid;
583  } else {
584  ‪BackendUtility::fixVersioningPid($elementTable, $elementRecord);
585  $pageUid = ($elementUid = $elementRecord['pid']);
586  }
587 
588  // new way, options are
589  // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
590  // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
591  $pageTsConfig = ‪BackendUtility::getPagesTSconfig($pageUid);
592  $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
593  $markers = [
594  '###RECORD_TITLE###' => $recordTitle,
595  '###RECORD_PATH###' => ‪BackendUtility::getRecordPath($elementUid, '', 20),
596  '###SITE_NAME###' => ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
597  '###SITE_URL###' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
598  '###WORKSPACE_TITLE###' => $workspaceRec['title'],
599  '###WORKSPACE_UID###' => $workspaceRec['uid'],
600  '###ELEMENT_NAME###' => $elementName,
601  '###NEXT_STAGE###' => $newStage,
602  '###COMMENT###' => $comment,
603  // See: #30212 - keep both markers for compatibility
604  '###USER_REALNAME###' => $dataHandler->BE_USER->user['realName'],
605  '###USER_FULLNAME###' => $dataHandler->BE_USER->user['realName'],
606  '###USER_USERNAME###' => $dataHandler->BE_USER->user['username']
607  ];
608  // add marker for preview links if workspace extension is loaded
609  $this->workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
610  // only generate the link if the marker is in the template - prevents database from getting to much entries
611  if (GeneralUtility::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
612  $tempEmailMessage = $this->‪getLanguageService()->‪sL($emailConfig['message']);
613  } else {
614  $tempEmailMessage = $emailConfig['message'];
615  }
616  if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== false) {
617  $markers['###PREVIEW_LINK###'] = $previewUriBuilder->buildUriForPage((int)$elementUid, 0);
618  }
619  unset($tempEmailMessage);
620 
621  $markers['###SPLITTED_PREVIEW_LINK###'] = $previewUriBuilder->buildUriForWorkspaceSplitPreview((int)$elementUid, true);
622  // Hook for preprocessing of the content for formmails:
623  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] ?? [] as $className) {
624  $_procObj = GeneralUtility::makeInstance($className);
625  $markers = $_procObj->postModifyMarkers($markers, $this);
626  }
627  // send an email to each individual user, to ensure the
628  // multilanguage version of the email
629  $emailRecipients = [];
630  // an array of language objects that are needed
631  // for emails with different languages
632  $languageObjects = [
633  $this->‪getLanguageService()->lang => $this->‪getLanguageService()
634  ];
635  // loop through each recipient and send the email
636  foreach ($emails as $recipientData) {
637  // don't send an email twice
638  if (isset($emailRecipients[$recipientData['email']])) {
639  continue;
640  }
641  $emailSubject = $emailConfig['subject'];
642  $emailMessage = $emailConfig['message'];
643  $emailRecipients[$recipientData['email']] = $recipientData['email'];
644  // check if the email needs to be localized
645  // in the users' language
646  if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:') || GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
647  $recipientLanguage = $recipientData['lang'] ? $recipientData['lang'] : 'default';
648  if (!isset($languageObjects[$recipientLanguage])) {
649  // a LANG object in this language hasn't been
650  // instantiated yet, so this is done here
651  $languageObject = GeneralUtility::makeInstance(LanguageService::class);
652  $languageObject->init($recipientLanguage);
653  $languageObjects[$recipientLanguage] = $languageObject;
654  } else {
655  $languageObject = $languageObjects[$recipientLanguage];
656  }
657  if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:')) {
658  $emailSubject = $languageObject->sL($emailSubject);
659  }
660  if (GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
661  $emailMessage = $languageObject->sL($emailMessage);
662  }
663  }
664  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
665  $emailSubject = $templateService->substituteMarkerArray($emailSubject, $markers, '', true, true);
666  $emailMessage = $templateService->substituteMarkerArray($emailMessage, $markers, '', true, true);
667  // Send an email to the recipient
669  $mail = GeneralUtility::makeInstance(\‪TYPO3\CMS\Core\Mail\MailMessage::class);
670  if (!empty($recipientData['realName'])) {
671  $recipient = [$recipientData['email'] => $recipientData['realName']];
672  } else {
673  $recipient = $recipientData['email'];
674  }
675  $mail->setTo($recipient)
676  ->setSubject($emailSubject)
677  ->setBody($emailMessage);
678  $mail->send();
679  }
680  $emailRecipients = implode(',', $emailRecipients);
681  if ($dataHandler->enableLogging) {
682  $propertyArray = $dataHandler->‪getRecordProperties($table, $id);
683  $pid = $propertyArray['pid'];
684  $dataHandler->‪log($table, $id, 0, 0, 0, 'Notification email for stage change was sent to "' . $emailRecipients . '"', -1, [], $dataHandler->‪eventPid($table, $id, $pid));
685  }
686  }
687  }
688 
697  protected function ‪getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = false)
698  {
699  $users = GeneralUtility::trimExplode(',', $listOfUsers, true);
700  $emails = [];
701  foreach ($users as $userIdent) {
702  if ($noTablePrefix) {
703  $id = (int)$userIdent;
704  } else {
705  [$table, $id] = GeneralUtility::revExplode('_', $userIdent, 2);
706  }
707  if ($table === 'be_users' || $noTablePrefix) {
708  if ($userRecord = ‪BackendUtility::getRecord('be_users', $id, 'uid,email,lang,realName', ‪BackendUtility::BEenableFields('be_users'))) {
709  if (trim($userRecord['email']) !== '') {
710  $emails[$id] = $userRecord;
711  }
712  }
713  }
714  }
715  return $emails;
716  }
717 
718  /****************************
719  ***** Stage Changes ******
720  ****************************/
732  protected function ‪version_setStage($table, $id, $stageId, $comment = '', ‪$notificationEmailInfo = false, ‪DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
733  {
734  if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
735  $dataHandler->‪newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
736  } elseif ($dataHandler->‪checkRecordUpdateAccess($table, $id)) {
737  $record = ‪BackendUtility::getRecord($table, $id);
738  $stat = $dataHandler->BE_USER->checkWorkspace($record['t3ver_wsid']);
739  // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
740  if ($dataHandler->BE_USER->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
741  // Set stage of record:
742  GeneralUtility::makeInstance(ConnectionPool::class)
743  ->getConnectionForTable($table)
744  ->update(
745  $table,
746  [
747  't3ver_stage' => $stageId,
748  ],
749  ['uid' => (int)$id]
750  );
751 
752  if ($dataHandler->enableLogging) {
753  $propertyArray = $dataHandler->‪getRecordProperties($table, $id);
754  $pid = $propertyArray['pid'];
755  $dataHandler->‪log($table, $id, 0, 0, 0, 'Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', -1, [], $dataHandler->‪eventPid($table, $id, $pid));
756  }
757  // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
758  $dataHandler->‪log($table, $id, 6, 0, 0, 'Stage raised...', 30, ['comment' => $comment, 'stage' => $stageId]);
759  if ((int)$stat['stagechg_notification'] > 0) {
761  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = [$stat, $stageId, $comment];
762  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
763  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
764  } else {
765  $this->‪notifyStageChange($stat, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
766  }
767  }
768  } else {
769  $dataHandler->‪newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
770  }
771  } else {
772  $dataHandler->‪newlog('Attempt to set stage for record failed because you do not have edit access', 1);
773  }
774  }
775 
776  /*****************************
777  ***** CMD versioning ******
778  *****************************/
779 
793  protected function ‪version_swap($table, $id, $swapWith, $swapIntoWS = false, ‪DataHandler $dataHandler, $comment = '', ‪$notificationEmailInfo = false, $notificationAlternativeRecipients = [])
794  {
795 
796  // Check prerequisites before start swapping
797 
798  // Skip records that have been deleted during the current execution
799  if ($dataHandler->‪hasDeletedRecord($table, $id)) {
800  return;
801  }
802 
803  // First, check if we may actually edit the online record
804  if (!$dataHandler->‪checkRecordUpdateAccess($table, $id)) {
805  $dataHandler->‪newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
806  return;
807  }
808  // Select the two versions:
809  $curVersion = ‪BackendUtility::getRecord($table, $id, '*');
810  $swapVersion = ‪BackendUtility::getRecord($table, $swapWith, '*');
811  $movePlh = [];
812  $movePlhID = 0;
813  if (!(is_array($curVersion) && is_array($swapVersion))) {
814  $dataHandler->‪newlog('Error: Either online or swap version could not be selected!', 2);
815  return;
816  }
817  if (!$dataHandler->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
818  $dataHandler->‪newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
819  return;
820  }
821  $wsAccess = $dataHandler->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
822  if (!($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10)) {
823  $dataHandler->‪newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
824  return;
825  }
826  if (!($dataHandler->‪doesRecordExist($table, $swapWith, 'show') && $dataHandler->‪checkRecordUpdateAccess($table, $swapWith))) {
827  $dataHandler->‪newlog('You cannot publish a record you do not have edit and show permissions for', 1);
828  return;
829  }
830  if ($swapIntoWS && !$dataHandler->BE_USER->workspaceSwapAccess()) {
831  $dataHandler->‪newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
832  return;
833  }
834  // Check if the swapWith record really IS a version of the original!
835  if (!(((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0) && (int)$swapVersion['t3ver_oid'] === (int)$id)) {
836  $dataHandler->‪newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!', 2);
837  return;
838  }
839  // Lock file name:
840  $lockFileName = ‪Environment::getVarPath() . '/lock/swap' . $table . '_' . $id . '.ser';
841  if (@is_file($lockFileName)) {
842  $dataHandler->‪newlog('A swapping lock file was present. Either another swap process is already running or a previous swap process failed. Ask your administrator to handle the situation.', 2);
843  return;
844  }
845 
846  // Now start to swap records by first creating the lock file
847 
848  // Write lock-file:
849  GeneralUtility::writeFileToTypo3tempDir($lockFileName, serialize([
850  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
851  'user' => $dataHandler->BE_USER->user['username'],
852  'curVersion' => $curVersion,
853  'swapVersion' => $swapVersion
854  ]));
855  // Find fields to keep
856  $keepFields = $this->‪getUniqueFields($table);
857  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby']) {
858  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'];
859  }
860  // l10n-fields must be kept otherwise the localization
861  // will be lost during the publishing
862  if (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
863  $keepFields[] = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
864  }
865  // Swap "keepfields"
866  foreach ($keepFields as $fN) {
867  $tmp = $swapVersion[$fN];
868  $swapVersion[$fN] = $curVersion[$fN];
869  $curVersion[$fN] = $tmp;
870  }
871  // Preserve states:
872  $t3ver_state = [];
873  $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
874  $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
875  // Modify offline version to become online:
876  $tmp_wsid = $swapVersion['t3ver_wsid'];
877  // Set pid for ONLINE
878  $swapVersion['pid'] = (int)$curVersion['pid'];
879  // We clear this because t3ver_oid only make sense for offline versions
880  // and we want to prevent unintentional misuse of this
881  // value for online records.
882  $swapVersion['t3ver_oid'] = 0;
883  // In case of swapping and the offline record has a state
884  // (like 2 or 4 for deleting or move-pointer) we set the
885  // current workspace ID so the record is not deselected
886  // in the interface by BackendUtility::versioningPlaceholderClause()
887  $swapVersion['t3ver_wsid'] = 0;
888  if ($swapIntoWS) {
889  if ($t3ver_state['swapVersion'] > 0) {
890  $swapVersion['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
891  } else {
892  $swapVersion['t3ver_wsid'] = (int)$curVersion['t3ver_wsid'];
893  }
894  }
895  $swapVersion['t3ver_tstamp'] = ‪$GLOBALS['EXEC_TIME'];
896  $swapVersion['t3ver_stage'] = 0;
897  if (!$swapIntoWS) {
898  $swapVersion['t3ver_state'] = (string)new ‪VersionState(‪VersionState::DEFAULT_STATE);
899  }
900  // Moving element.
902  // && $t3ver_state['swapVersion']==4 // Maybe we don't need this?
903  if ($plhRec = ‪BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . (‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
904  $movePlhID = $plhRec['uid'];
905  $movePlh['pid'] = $swapVersion['pid'];
906  $swapVersion['pid'] = (int)$plhRec['pid'];
907  $curVersion['t3ver_state'] = (int)$swapVersion['t3ver_state'];
908  $swapVersion['t3ver_state'] = (string)new ‪VersionState(‪VersionState::DEFAULT_STATE);
909  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby']) {
910  // sortby is a "keepFields" which is why this will work...
911  $movePlh[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']];
912  $swapVersion[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[‪$GLOBALS['TCA'][$table]['ctrl']['sortby']];
913  }
914  }
915  }
916  // Take care of relations in each field (e.g. IRRE):
917  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
918  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
919  if (isset($fieldConf['config']) && is_array($fieldConf['config'])) {
920  $this->‪version_swap_processFields($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $dataHandler);
921  }
922  }
923  }
924  unset($swapVersion['uid']);
925  // Modify online version to become offline:
926  unset($curVersion['uid']);
927  // Set pid for OFFLINE
928  $curVersion['pid'] = -1;
929  $curVersion['t3ver_oid'] = (int)$id;
930  $curVersion['t3ver_wsid'] = $swapIntoWS ? (int)$tmp_wsid : 0;
931  $curVersion['t3ver_tstamp'] = ‪$GLOBALS['EXEC_TIME'];
932  $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
933  // Increment lifecycle counter
934  $curVersion['t3ver_stage'] = 0;
935  if (!$swapIntoWS) {
936  $curVersion['t3ver_state'] = (string)new ‪VersionState(‪VersionState::DEFAULT_STATE);
937  }
938  // Registering and swapping MM relations in current and swap records:
939  $dataHandler->‪version_remapMMForVersionSwap($table, $id, $swapWith);
940  // Generating proper history data to prepare logging
941  $dataHandler->‪compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
942  $dataHandler->‪compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
943 
944  // Execute swapping:
945  $sqlErrors = [];
946  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
947 
948  $platform = $connection->getDatabasePlatform();
949  $tableDetails = null;
950  if ($platform instanceof SQLServerPlatform) {
951  // mssql needs to set proper PARAM_LOB and others to update fields
952  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
953  }
954 
955  try {
956  $types = [];
957 
958  if ($platform instanceof SQLServerPlatform) {
959  foreach ($curVersion as $columnName => $columnValue) {
960  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
961  }
962  }
963 
964  $connection->update(
965  $table,
966  $swapVersion,
967  ['uid' => (int)$id],
968  $types
969  );
970  } catch (DBALException $e) {
971  $sqlErrors[] = $e->getPrevious()->getMessage();
972  }
973 
974  if (empty($sqlErrors)) {
975  try {
976  $types = [];
977  if ($platform instanceof SQLServerPlatform) {
978  foreach ($curVersion as $columnName => $columnValue) {
979  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
980  }
981  }
982 
983  $connection->update(
984  $table,
985  $curVersion,
986  ['uid' => (int)$swapWith],
987  $types
988  );
989  unlink($lockFileName);
990  } catch (DBALException $e) {
991  $sqlErrors[] = $e->getPrevious()->getMessage();
992  }
993  }
994 
995  if (!empty($sqlErrors)) {
996  $dataHandler->‪newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
997  } else {
998  // Register swapped ids for later remapping:
999  $this->remappedIds[$table][$id] = $swapWith;
1000  $this->remappedIds[$table][$swapWith] = $id;
1001  // If a moving operation took place...:
1002  if ($movePlhID) {
1003  // Remove, if normal publishing:
1004  if (!$swapIntoWS) {
1005  // For delete + completely delete!
1006  $dataHandler->‪deleteEl($table, $movePlhID, true, true);
1007  } else {
1008  // Otherwise update the movePlaceholder:
1009  GeneralUtility::makeInstance(ConnectionPool::class)
1010  ->getConnectionForTable($table)
1011  ->update(
1012  $table,
1013  $movePlh,
1014  ['uid' => (int)$movePlhID]
1015  );
1016  $dataHandler->‪addRemapStackRefIndex($table, $movePlhID);
1017  }
1018  }
1019  // Checking for delete:
1020  // Delete only if new/deleted placeholders are there.
1021  if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) {
1022  // Force delete
1023  $dataHandler->‪deleteEl($table, $id, true);
1024  }
1025  if ($dataHandler->enableLogging) {
1026  $dataHandler->‪log($table, $id, 0, 0, 0, ($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, -1, [], $dataHandler->‪eventPid($table, $id, $swapVersion['pid']));
1027  }
1028 
1029  // Update reference index of the live record:
1030  $dataHandler->‪addRemapStackRefIndex($table, $id);
1031  // Set log entry for live record:
1032  $propArr = $dataHandler->‪getRecordPropertiesFromRow($table, $swapVersion);
1033  if ($propArr['_ORIG_pid'] == -1) {
1034  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
1035  } else {
1036  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
1037  }
1038  $theLogId = $dataHandler->‪log($table, $id, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
1039  $dataHandler->‪setHistory($table, $id, $theLogId);
1040  // Update reference index of the offline record:
1041  $dataHandler->‪addRemapStackRefIndex($table, $swapWith);
1042  // Set log entry for offline record:
1043  $propArr = $dataHandler->‪getRecordPropertiesFromRow($table, $curVersion);
1044  if ($propArr['_ORIG_pid'] == -1) {
1045  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
1046  } else {
1047  $label = $this->‪getLanguageService()->‪sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
1048  }
1049  $theLogId = $dataHandler->‪log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $swapWith], $propArr['event_pid']);
1050  $dataHandler->‪setHistory($table, $swapWith, $theLogId);
1051 
1052  $stageId = -20; // \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID;
1054  $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
1055  $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
1056  $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = $table . ':' . $id;
1057  $this->notificationEmailInfo[$notificationEmailInfoKey]['alternativeRecipients'] = $notificationAlternativeRecipients;
1058  } else {
1059  $this->‪notifyStageChange($wsAccess, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
1060  }
1061  // Write to log with stageId -20
1062  if ($dataHandler->enableLogging) {
1063  $propArr = $dataHandler->‪getRecordProperties($table, $id);
1064  $pid = $propArr['pid'];
1065  $dataHandler->‪log($table, $id, 0, 0, 0, 'Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', -1, [], $dataHandler->‪eventPid($table, $id, $pid));
1066  }
1067  $dataHandler->‪log($table, $id, 6, 0, 0, 'Published', 30, ['comment' => $comment, 'stage' => $stageId]);
1068 
1069  // Clear cache:
1070  $dataHandler->‪registerRecordIdForPageCacheClearing($table, $id);
1071  // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
1072  if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
1073  // For delete + completely delete!
1074  if ($table === 'pages') {
1075  // Note on fifth argument false: At this point both $curVersion and $swapVersion page records are
1076  // identical in DB. deleteEl() would now usually find all records assigned to our obsolete
1077  // page which at the same time belong to our current version page, and would delete them.
1078  // To suppress this, false tells deleteEl() to only delete the obsolete page but not its assigned records.
1079  $dataHandler->‪deleteEl($table, $swapWith, true, true, false);
1080  } else {
1081  $dataHandler->‪deleteEl($table, $swapWith, true, true);
1082  }
1083  }
1084 
1085  //Update reference index for live workspace too:
1087  $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
1088  $refIndexObj->setWorkspaceId(0);
1089  $refIndexObj->updateRefIndexTable($table, $id);
1090  $refIndexObj->updateRefIndexTable($table, $swapWith);
1091  }
1092  }
1093 
1101  public function ‪writeRemappedForeignField(‪RelationHandler $dbAnalysis, array $configuration, $parentId)
1102  {
1103  foreach ($dbAnalysis->itemArray as &$item) {
1104  if (isset($this->remappedIds[$item['table']][$item['id']])) {
1105  $item['id'] = $this->remappedIds[$item['table']][$item['id']];
1106  }
1107  }
1108  $dbAnalysis->‪writeForeignField($configuration, $parentId);
1109  }
1110 
1122  protected function ‪version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, ‪DataHandler $dataHandler)
1123  {
1124  $inlineType = $dataHandler->‪getInlineFieldType($configuration);
1125  if ($inlineType !== 'field') {
1126  return;
1127  }
1128  $foreignTable = $configuration['foreign_table'];
1129  // Read relations that point to the current record (e.g. live record):
1130  $liveRelations = $this->‪createRelationHandlerInstance();
1131  $liveRelations->setWorkspaceId(0);
1132  $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
1133  // Read relations that point to the record to be swapped with e.g. draft record):
1134  $versionRelations = $this->‪createRelationHandlerInstance();
1135  $versionRelations->setUseLiveReferenceIds(false);
1136  $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
1137  // Update relations for both (workspace/versioning) sites:
1138  if (count($liveRelations->itemArray)) {
1139  $dataHandler->‪addRemapAction(
1140  $tableName,
1141  $liveData['uid'],
1142  [$this, 'updateInlineForeignFieldSorting'],
1143  [$tableName, $liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace]
1144  );
1145  }
1146  if (count($versionRelations->itemArray)) {
1147  $dataHandler->‪addRemapAction(
1148  $tableName,
1149  $liveData['uid'],
1150  [$this, 'updateInlineForeignFieldSorting'],
1151  [$tableName, $liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0]
1152  );
1153  }
1154  }
1155 
1173  public function ‪updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
1174  {
1175  ‪$remappedIds = [];
1176  // Use remapped ids (live id <-> version id)
1177  foreach ($foreignIds as $foreignId) {
1178  if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
1179  ‪$remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
1180  } else {
1181  ‪$remappedIds[] = $foreignId;
1182  }
1183  }
1184 
1185  $relationHandler = $this->‪createRelationHandlerInstance();
1186  $relationHandler->setWorkspaceId($targetWorkspaceId);
1187  $relationHandler->setUseLiveReferenceIds(false);
1188  $relationHandler->start(implode(',', ‪$remappedIds), $foreignTableName);
1189  $relationHandler->processDeletePlaceholder();
1190  $relationHandler->writeForeignField($configuration, $parentId);
1191  }
1192 
1201  protected function ‪version_clearWSID($table, $id, $flush = false, ‪DataHandler $dataHandler)
1202  {
1203  if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
1204  $dataHandler->‪newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
1205  return;
1206  }
1207  if (!$dataHandler->‪checkRecordUpdateAccess($table, $id)) {
1208  $dataHandler->‪newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
1209  return;
1210  }
1211  $liveRec = ‪BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
1212  if (!$liveRec) {
1213  return;
1214  }
1215  // Clear workspace ID:
1216  $updateData = [
1217  't3ver_wsid' => 0,
1218  't3ver_tstamp' => ‪$GLOBALS['EXEC_TIME']
1219  ];
1220  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
1221  $connection->update(
1222  $table,
1223  $updateData,
1224  ['uid' => (int)$id]
1225  );
1226 
1227  // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
1228  if (
1229  ‪VersionState::cast($liveRec['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER)
1230  || ‪VersionState::cast($liveRec['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)
1231  ) {
1232  $connection->update(
1233  $table,
1234  $updateData,
1235  ['uid' => (int)$liveRec['uid']]
1236  );
1237 
1238  // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
1239  $dataHandler->‪deleteEl($table, $liveRec['uid'], true);
1240  }
1241  // If "deleted" flag is set for the version that got released
1242  // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
1243  $wsRec = ‪BackendUtility::getRecord($table, $id);
1244  if (
1245  $flush
1246  || (
1247  ‪VersionState::cast($wsRec['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER)
1248  || ‪VersionState::cast($wsRec['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)
1249  )
1250  ) {
1251  $dataHandler->‪deleteEl($table, $id, true, true);
1252  }
1253  // Remove the move-placeholder if found for live record.
1255  if ($plhRec = ‪BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
1256  $dataHandler->‪deleteEl($table, $plhRec['uid'], true, true);
1257  }
1258  }
1259  }
1260 
1268  protected function ‪resetStageOfElements($stageId)
1269  {
1270  foreach ($this->‪getTcaTables() as $tcaTable) {
1272  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1273  ->getQueryBuilderForTable($tcaTable);
1274 
1275  $queryBuilder
1276  ->update($tcaTable)
1277  ->set('t3ver_stage', ‪StagesService::STAGE_EDIT_ID)
1278  ->where(
1279  $queryBuilder->expr()->eq(
1280  't3ver_stage',
1281  $queryBuilder->createNamedParameter($stageId, \PDO::PARAM_INT)
1282  ),
1283  $queryBuilder->expr()->eq(
1284  'pid',
1285  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1286  ),
1287  $queryBuilder->expr()->gt(
1288  't3ver_wsid',
1289  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1290  )
1291  )
1292  ->execute();
1293  }
1294  }
1295  }
1296 
1302  protected function ‪flushWorkspaceElements($workspaceId)
1303  {
1304  $command = [];
1305  foreach ($this->‪getTcaTables() as $tcaTable) {
1307  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1308  ->getQueryBuilderForTable($tcaTable);
1309  $queryBuilder->getRestrictions()
1310  ->removeAll()
1311  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1312  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, $workspaceId, false));
1313 
1314  $result = $queryBuilder
1315  ->select('uid')
1316  ->from($tcaTable)
1317  ->orderBy('uid')
1318  ->execute();
1319 
1320  while (($recordId = $result->fetchColumn()) !== false) {
1321  $command[$tcaTable][$recordId]['version']['action'] = 'flush';
1322  }
1323  }
1324  }
1325  if (!empty($command)) {
1326  $dataHandler = $this->‪getDataHandler();
1327  $dataHandler->‪start([], $command);
1328  $dataHandler->‪process_cmdmap();
1329  }
1330  }
1331 
1337  protected function ‪getTcaTables()
1338  {
1339  return array_keys(‪$GLOBALS['TCA']);
1340  }
1341 
1345  protected function ‪getDataHandler()
1346  {
1347  return GeneralUtility::makeInstance(DataHandler::class);
1348  }
1349 
1355  protected function ‪flushWorkspaceCacheEntriesByWorkspaceId($workspaceId)
1356  {
1357  $workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
1358  $workspacesCache->flushByTag($workspaceId);
1359  $workspacesCache->flushByTag(‪WorkspaceService::SELECT_ALL_WORKSPACES);
1360  }
1361 
1362  /*******************************
1363  ***** helper functions ******
1364  *******************************/
1365 
1374  public function ‪findPageElementsForVersionSwap($table, $id, $offlineId)
1375  {
1376  $rec = ‪BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
1377  $workspaceId = (int)$rec['t3ver_wsid'];
1378  $elementData = [];
1379  if ($workspaceId === 0) {
1380  return $elementData;
1381  }
1382  // Get page UID for LIVE and workspace
1383  if ($table !== 'pages') {
1384  $rec = ‪BackendUtility::getRecord($table, $id, 'pid');
1385  $pageId = $rec['pid'];
1386  $rec = ‪BackendUtility::getRecord('pages', $pageId);
1387  ‪BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1388  $offlinePageId = $rec['_ORIG_uid'];
1389  } else {
1390  $pageId = $id;
1391  $offlinePageId = $offlineId;
1392  }
1393  // Traversing all tables supporting versioning:
1394  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1395  if (‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1396  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1397  ->getQueryBuilderForTable($table);
1398 
1399  $queryBuilder->getRestrictions()
1400  ->removeAll()
1401  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1402 
1403  $statement = $queryBuilder
1404  ->select('A.uid AS offlineUid', 'B.uid AS uid')
1405  ->from($table, 'A')
1406  ->from($table, 'B')
1407  ->where(
1408  $queryBuilder->expr()->eq(
1409  'A.pid',
1410  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1411  ),
1412  $queryBuilder->expr()->eq(
1413  'B.pid',
1414  $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
1415  ),
1416  $queryBuilder->expr()->eq(
1417  'A.t3ver_wsid',
1418  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1419  ),
1420  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1421  )
1422  ->execute();
1423 
1424  while ($row = $statement->fetch()) {
1425  $elementData[$table][] = [$row['uid'], $row['offlineUid']];
1426  }
1427  }
1428  }
1429  if ($offlinePageId && $offlinePageId != $pageId) {
1430  $elementData['pages'][] = [$pageId, $offlinePageId];
1431  }
1432 
1433  return $elementData;
1434  }
1435 
1443  public function ‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
1444  {
1445  if ($workspaceId == 0) {
1446  return;
1447  }
1448  // Traversing all tables supporting versioning:
1449  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
1450  if (‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1451  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1452  ->getQueryBuilderForTable($table);
1453 
1454  $queryBuilder->getRestrictions()
1455  ->removeAll()
1456  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1457 
1458  $statement = $queryBuilder
1459  ->select('A.uid')
1460  ->from($table, 'A')
1461  ->from($table, 'B')
1462  ->where(
1463  $queryBuilder->expr()->eq(
1464  'A.pid',
1465  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1466  ),
1467  $queryBuilder->expr()->in(
1468  'B.pid',
1469  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
1470  ),
1471  $queryBuilder->expr()->eq(
1472  'A.t3ver_wsid',
1473  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1474  ),
1475  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1476  )
1477  ->groupBy('A.uid')
1478  ->execute();
1479 
1480  while ($row = $statement->fetch()) {
1481  $elementList[$table][] = $row['uid'];
1482  }
1483  if (is_array($elementList[$table])) {
1484  // Yes, it is possible to get non-unique array even with DISTINCT above!
1485  // It happens because several UIDs are passed in the array already.
1486  $elementList[$table] = array_unique($elementList[$table]);
1487  }
1488  }
1489  }
1490  }
1491 
1501  public function ‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
1502  {
1503  if ($workspaceId == 0) {
1504  return;
1505  }
1506 
1507  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1508  ->getQueryBuilderForTable($table);
1509  $queryBuilder->getRestrictions()
1510  ->removeAll()
1511  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1512 
1513  $statement = $queryBuilder
1514  ->select('B.pid')
1515  ->from($table, 'A')
1516  ->from($table, 'B')
1517  ->where(
1518  $queryBuilder->expr()->eq(
1519  'A.pid',
1520  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1521  ),
1522  $queryBuilder->expr()->eq(
1523  'A.t3ver_wsid',
1524  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1525  ),
1526  $queryBuilder->expr()->in(
1527  'A.uid',
1528  $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
1529  ),
1530  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
1531  )
1532  ->groupBy('B.pid')
1533  ->execute();
1534 
1535  while ($row = $statement->fetch()) {
1536  $pageIdList[] = $row['pid'];
1537  // Find ws version
1538  // Note: cannot use BackendUtility::getRecordWSOL()
1539  // here because it does not accept workspace id!
1540  $rec = ‪BackendUtility::getRecord('pages', $row[0]);
1541  ‪BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1542  if ($rec['_ORIG_uid']) {
1543  $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
1544  }
1545  }
1546  // The line below is necessary even with DISTINCT
1547  // because several elements can be passed by caller
1548  $pageIdList = array_unique($pageIdList);
1549  }
1550 
1556  public function ‪findRealPageIds(array &$idList)
1557  {
1558  foreach ($idList as $key => $id) {
1559  $rec = ‪BackendUtility::getRecord('pages', $id, 't3ver_oid');
1560  if ($rec['t3ver_oid'] > 0) {
1561  $idList[$key] = $rec['t3ver_oid'];
1562  }
1563  }
1564  }
1565 
1579  protected function ‪moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, ‪DataHandler $dataHandler)
1580  {
1581  // If a record gets moved after a record that already has a placeholder record
1582  // then the new placeholder record needs to be after the existing one
1583  $originalRecordDestinationPid = $destPid;
1584  if ($destPid < 0) {
1585  $movePlaceHolder = ‪BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
1586  if ($movePlaceHolder !== false) {
1587  $destPid = -$movePlaceHolder['uid'];
1588  }
1589  }
1590  if ($plh = ‪BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
1591  // If already a placeholder exists, move it:
1592  $dataHandler->‪moveRecord_raw($table, $plh['uid'], $destPid);
1593  } else {
1594  // First, we create a placeholder record in the Live workspace that
1595  // represents the position to where the record is eventually moved to.
1596  $newVersion_placeholderFieldArray = [];
1597 
1598  $factory = GeneralUtility::makeInstance(
1599  PlaceholderShadowColumnsResolver::class,
1600  $table,
1601  ‪$GLOBALS['TCA'][$table] ?? []
1602  );
1603  $shadowColumns = $factory->forMovePlaceholder();
1604  // Set values from the versioned record to the move placeholder
1605  if (!empty($shadowColumns)) {
1606  $versionedRecord = ‪BackendUtility::getRecord($table, $wsUid);
1607  foreach ($shadowColumns as $shadowColumn) {
1608  if (isset($versionedRecord[$shadowColumn])) {
1609  $newVersion_placeholderFieldArray[$shadowColumn] = $versionedRecord[$shadowColumn];
1610  }
1611  }
1612  }
1613 
1614  if (‪$GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1615  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['crdate']] = ‪$GLOBALS['EXEC_TIME'];
1616  }
1617  if (‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1618  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $dataHandler->userid;
1619  }
1620  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1621  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
1622  }
1623  if ($table === 'pages') {
1624  // Copy page access settings from original page to placeholder
1625  $perms_clause = $dataHandler->BE_USER->getPagePermsClause(‪Permission::PAGE_SHOW);
1626  $access = ‪BackendUtility::readPageAccess($uid, $perms_clause);
1627  $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
1628  $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
1629  $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
1630  $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
1631  $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
1632  }
1633  $newVersion_placeholderFieldArray['t3ver_label'] = 'MovePlaceholder #' . $uid;
1634  $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
1635  // Setting placeholder state value for temporary record
1636  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new ‪VersionState(‪VersionState::MOVE_PLACEHOLDER);
1637  // Setting workspace - only so display of place holders can filter out those from other workspaces.
1638  $newVersion_placeholderFieldArray['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
1639  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['label']] = $dataHandler->‪getPlaceholderTitleForTableLabel($table, 'MOVE-TO PLACEHOLDER for #' . $uid);
1640  // moving localized records requires to keep localization-settings for the placeholder too
1641  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['languageField']) && isset(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
1642  $l10nParentRec = ‪BackendUtility::getRecord($table, $uid);
1643  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']];
1644  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1645  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])) {
1646  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = $l10nParentRec[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']];
1647  }
1648  unset($l10nParentRec);
1649  }
1650  // Initially, create at root level.
1651  $newVersion_placeholderFieldArray['pid'] = 0;
1652  $id = 'NEW_MOVE_PLH';
1653  // Saving placeholder as 'original'
1654  $dataHandler->‪insertDB($table, $id, $newVersion_placeholderFieldArray, false);
1655  // Move the new placeholder from temporary root-level to location:
1656  $dataHandler->‪moveRecord_raw($table, $dataHandler->substNEWwithIDs[$id], $destPid);
1657  // Move the workspace-version of the original to be the version of the move-to-placeholder:
1658  // Setting placeholder state value for version (so it can know it is currently a new version...)
1659  $updateFields = [
1660  't3ver_state' => (string)new ‪VersionState(‪VersionState::MOVE_POINTER)
1661  ];
1662 
1663  GeneralUtility::makeInstance(ConnectionPool::class)
1664  ->getConnectionForTable($table)
1665  ->update(
1666  $table,
1667  $updateFields,
1668  ['uid' => (int)$wsUid]
1669  );
1670  }
1671  // Check for the localizations of that element and move them as well
1672  $dataHandler->‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
1673  }
1674 
1681  public function ‪getCommandMap(‪DataHandler $dataHandler)
1682  {
1683  return GeneralUtility::makeInstance(
1684  CommandMap::class,
1685  $this,
1686  $dataHandler,
1687  $dataHandler->cmdmap,
1688  $dataHandler->BE_USER->workspace
1689  );
1690  }
1691 
1698  protected function ‪getUniqueFields($table)
1699  {
1700  $listArr = [];
1701  if (empty(‪$GLOBALS['TCA'][$table]['columns'])) {
1702  return $listArr;
1703  }
1704  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
1705  if ($configArr['config']['type'] === 'input') {
1706  $evalCodesArray = GeneralUtility::trimExplode(',', $configArr['config']['eval'], true);
1707  if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
1708  $listArr[] = $field;
1709  }
1710  }
1711  }
1712  return $listArr;
1713  }
1714 
1718  protected function ‪createRelationHandlerInstance()
1719  {
1720  return GeneralUtility::makeInstance(RelationHandler::class);
1721  }
1722 
1726  protected function ‪getLanguageService()
1727  {
1728  return ‪$GLOBALS['LANG'];
1729  }
1730 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getLanguageService
‪LanguageService getLanguageService()
Definition: DataHandlerHook.php:1723
‪TYPO3\CMS\Core\DataHandling\DataHandler\setHistory
‪setHistory($table, $id, $logId)
Definition: DataHandler.php:7495
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPlaceholderTitleForTableLabel
‪string getPlaceholderTitleForTableLabel($table, $placeholderContent=null)
Definition: DataHandler.php:1393
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord
‪moveRecord($table, $uid, $destPid)
Definition: DataHandler.php:4499
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_deleteAction
‪processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
Definition: DataHandlerHook.php:171
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:46
‪TYPO3\CMS\Core\DataHandling\DataHandler\getInlineFieldType
‪string bool getInlineFieldType($conf)
Definition: DataHandler.php:8501
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerRecordIdForPageCacheClearing
‪registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
Definition: DataHandler.php:8707
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteEl
‪deleteEl($table, $uid, $noRecordCheck=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:5203
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook
Definition: DataHandlerHook.php:47
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapStackRefIndex
‪addRemapStackRefIndex($table, $id)
Definition: DataHandler.php:6588
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\TABLE_WORKSPACE
‪const TABLE_WORKSPACE
Definition: WorkspaceService.php:43
‪TYPO3\CMS\Core\DataHandling\DataHandler\compareFieldArrayWithCurrentAndUnset
‪array compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
Definition: DataHandler.php:7992
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_processFields
‪moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
Definition: DataHandlerHook.php:404
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:32
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:50
‪TYPO3
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap
‪version_swap($table, $id, $swapWith, $swapIntoWS=false, DataHandler $dataHandler, $comment='', $notificationEmailInfo=false, $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:790
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap
Definition: CommandMap.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordCopied
‪bool isRecordCopied($table, $uid)
Definition: DataHandler.php:8679
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceElements
‪flushWorkspaceElements($workspaceId)
Definition: DataHandlerHook.php:1299
‪TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
Definition: BackendWorkspaceRestriction.php:28
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$notificationEmailInfo
‪array $notificationEmailInfo
Definition: DataHandlerHook.php:54
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getDataHandler
‪DataHandler getDataHandler()
Definition: DataHandlerHook.php:1342
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:54
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:158
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getEmailsForStageChangeNotification
‪array getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix=false)
Definition: DataHandlerHook.php:694
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:72
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap\process
‪CommandMap process()
Definition: CommandMap.php:227
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_beforeStart
‪processCmdmap_beforeStart(DataHandler $dataHandler)
Definition: DataHandlerHook.php:74
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:23
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_swap_processFields
‪version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1119
‪TYPO3\CMS\Core\DataHandling\PlaceholderShadowColumnsResolver
Definition: PlaceholderShadowColumnsResolver.php:34
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4493
‪TYPO3\CMS\Core\DataHandling\DataHandler\hasDeletedRecord
‪bool hasDeletedRecord($tableName, $uid)
Definition: DataHandler.php:9188
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_setStage
‪version_setStage($table, $id, $stageId, $comment='', $notificationEmailInfo=false, DataHandler $dataHandler, array $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:729
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:182
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveL10nOverlayRecords
‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
Definition: DataHandler.php:4798
‪TYPO3\CMS\Backend\Utility\BackendUtility\fixVersioningPid
‪static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=false)
Definition: BackendUtility.php:3986
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\SELECT_ALL_WORKSPACES
‪const SELECT_ALL_WORKSPACES
Definition: WorkspaceService.php:44
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getUniqueFields
‪array getUniqueFields($table)
Definition: DataHandlerHook.php:1695
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_postProcess
‪processCmdmap_postProcess($command, $table, $id, $value, DataHandler $dataHandler)
Definition: DataHandlerHook.php:295
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap
‪processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $dataHandler)
Definition: DataHandlerHook.php:92
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_processFieldValue
‪moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration)
Definition: DataHandlerHook.php:437
‪TYPO3\CMS\Workspaces\DataHandler\CommandMap\get
‪array get()
Definition: CommandMap.php:101
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\notifyStageChange
‪notifyStageChange(array $stat, $stageId, $table, $id, $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients=[])
Definition: DataHandlerHook.php:482
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionOfRecord
‪static array null getLiveVersionOfRecord($table, $uid, $fields=' *')
Definition: BackendUtility.php:4213
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\writeRemappedForeignField
‪writeRemappedForeignField(RelationHandler $dbAnalysis, array $configuration, $parentId)
Definition: DataHandlerHook.php:1098
‪TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder
Definition: PreviewUriBuilder.php:39
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionizeRecord
‪int null versionizeRecord($table, $id, $label, $delete=false)
Definition: DataHandler.php:5878
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1811
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_raw
‪moveRecord_raw($table, $uid, $destPid)
Definition: DataHandler.php:4576
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageIdsForVersionStateChange
‪findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
Definition: DataHandlerHook.php:1498
‪TYPO3\CMS\Core\Database\RelationHandler\writeForeignField
‪writeForeignField($conf, $parentUid, $updateToUid=0, $skipSorting=false)
Definition: RelationHandler.php:1020
‪TYPO3\CMS\Core\DataHandling\DataHandler\eventPid
‪int eventPid($table, $uid, $pid)
Definition: DataHandler.php:7235
‪TYPO3\CMS\Backend\Utility\BackendUtility\getMovePlaceholder
‪static array bool getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
Definition: BackendUtility.php:4311
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:34
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$remappedIds
‪array $remappedIds
Definition: DataHandlerHook.php:60
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteL10nOverlayRecords
‪deleteL10nOverlayRecords($table, $uid)
Definition: DataHandler.php:5820
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordUpdateAccess
‪bool checkRecordUpdateAccess($table, $id, $data=false, $hookObjectsArr=null)
Definition: DataHandler.php:6689
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:32
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist
‪bool doesRecordExist($table, $id, $perms)
Definition: DataHandler.php:6814
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_cmdmap
‪void bool process_cmdmap()
Definition: DataHandler.php:3403
‪TYPO3\CMS\Backend\Utility\BackendUtility\BEenableFields
‪static string BEenableFields($table, $inv=false)
Definition: BackendUtility.php:256
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Backend\Utility\BackendUtility\getWorkspaceVersionOfRecord
‪static array bool getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
Definition: BackendUtility.php:4166
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:34
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord
‪moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
Definition: DataHandlerHook.php:319
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_EDIT_ID
‪const STAGE_EDIT_ID
Definition: StagesService.php:37
‪TYPO3\CMS\Workspaces\Service\StagesService
Definition: StagesService.php:31
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap
‪version_remapMMForVersionSwap($table, $id, $swapWith)
Definition: DataHandler.php:5985
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\createRelationHandlerInstance
‪RelationHandler createRelationHandlerInstance()
Definition: DataHandlerHook.php:1715
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getCommandMap
‪CommandMap getCommandMap(DataHandler $dataHandler)
Definition: DataHandlerHook.php:1678
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\flushWorkspaceCacheEntriesByWorkspaceId
‪flushWorkspaceCacheEntriesByWorkspaceId($workspaceId)
Definition: DataHandlerHook.php:1352
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\processCmdmap_afterFinish
‪processCmdmap_afterFinish(DataHandler $dataHandler)
Definition: DataHandlerHook.php:148
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\updateInlineForeignFieldSorting
‪updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
Definition: DataHandlerHook.php:1170
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapAction
‪addRemapAction($table, $id, array $callback, array $arguments)
Definition: DataHandler.php:6570
‪TYPO3\CMS\Core\DataHandling\DataHandler\start
‪start($data, $cmd, $altUserObject=null)
Definition: DataHandler.php:656
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
Definition: BackendUtility.php:864
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\version_clearWSID
‪version_clearWSID($table, $id, $flush=false, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1198
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:38
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:23
‪$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:4048
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:26
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordProperties
‪array getRecordProperties($table, $id, $noWSOL=false)
Definition: DataHandler.php:7198
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\resetStageOfElements
‪resetStageOfElements($stageId)
Definition: DataHandlerHook.php:1265
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Service\MarkerBasedTemplateService
Definition: MarkerBasedTemplateService.php:25
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordPath
‪static mixed getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
Definition: BackendUtility.php:572
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findRealPageIds
‪findRealPageIds(array &$idList)
Definition: DataHandlerHook.php:1553
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionSwap
‪array findPageElementsForVersionSwap($table, $id, $offlineId)
Definition: DataHandlerHook.php:1371
‪TYPO3\CMS\Workspaces\Service\StagesService\TABLE_STAGE
‪const TABLE_STAGE
Definition: StagesService.php:32
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\$workspaceService
‪WorkspaceService $workspaceService
Definition: DataHandlerHook.php:64
‪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:9040
‪TYPO3\CMS\Core\Utility\ArrayUtility\arrayDiffAssocRecursive
‪static array arrayDiffAssocRecursive(array $array1, array $array2)
Definition: ArrayUtility.php:728
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordPropertiesFromRow
‪array null getRecordPropertiesFromRow($table, $row)
Definition: DataHandler.php:7214
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertDB
‪int null insertDB($table, $id, $fieldArray, $newVersion=false, $suggestedUid=0, $dontSetNewIdIndex=false)
Definition: DataHandler.php:7325
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\moveRecord_wsPlaceholders
‪moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, DataHandler $dataHandler)
Definition: DataHandlerHook.php:1576
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array bool readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:635
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\findPageElementsForVersionStageChange
‪findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
Definition: DataHandlerHook.php:1440
‪TYPO3\CMS\Workspaces\Hook\DataHandlerHook\getTcaTables
‪array getTcaTables()
Definition: DataHandlerHook.php:1334
‪TYPO3\CMS\Core\DataHandling\DataHandler\newlog
‪int newlog($message, $error=0)
Definition: DataHandler.php:9068
‪TYPO3\CMS\Workspaces\Hook
Definition: BackendUtilityHook.php:3
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static string getVarPath()
Definition: Environment.php:165
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:71