TYPO3 CMS  TYPO3_6-2
DataHandlerHook.php
Go to the documentation of this file.
1 <?php
3 
22 
28 
36  protected $notificationEmailInfo = array();
37 
43  protected $generalComment = '';
44 
50  protected $remappedIds = array();
51 
52  /****************************
53  ***** Cmdmap Hooks ******
54  ****************************/
61  public function processCmdmap_beforeStart(DataHandler $tcemainObj) {
62  // Reset notification array
63  $this->notificationEmailInfo = array();
64  // Resolve dependencies of version/workspaces actions:
65  $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj)->process()->get();
66  }
67 
79  public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $tcemainObj) {
80  // custom command "version"
81  if ($command == 'version') {
82  $commandIsProcessed = TRUE;
83  $action = (string) $value['action'];
84  $comment = (isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment);
85  $notificationAlternativeRecipients = (isset($value['notificationAlternativeRecipients'])) && is_array($value['notificationAlternativeRecipients']) ? $value['notificationAlternativeRecipients'] : array();
86  switch ($action) {
87  case 'new':
88  // check if page / branch versioning is needed,
89  // or if "element" version can be used
90  $versionizeTree = -1;
91  if (isset($value['treeLevels'])) {
92  $versionizeTree = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value['treeLevels'], -1, 100);
93  }
94  if ($table == 'pages' && $versionizeTree >= 0) {
95  $this->versionizePages($id, $value['label'], $versionizeTree, $tcemainObj);
96  } else {
97  $tcemainObj->versionizeRecord($table, $id, $value['label']);
98  }
99  break;
100  case 'swap':
101  $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'],
102  $tcemainObj,
103  $comment,
104  TRUE,
105  $notificationAlternativeRecipients
106  );
107  break;
108  case 'clearWSID':
109  $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
110  break;
111  case 'flush':
112  $this->version_clearWSID($table, $id, TRUE, $tcemainObj);
113  break;
114  case 'setStage':
115  $elementIds = GeneralUtility::trimExplode(',', $id, TRUE);
116  foreach ($elementIds as $elementId) {
117  $this->version_setStage($table, $elementId, $value['stageId'],
118  $comment,
119  TRUE,
120  $tcemainObj,
121  $notificationAlternativeRecipients
122  );
123  }
124  break;
125  default:
126  // Do nothing
127  }
128  }
129  }
130 
138  public function processCmdmap_afterFinish(DataHandler $tcemainObj) {
139  // Empty accumulation array:
140  foreach ($this->notificationEmailInfo as $notifItem) {
141  $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $tcemainObj, $notifItem['alternativeRecipients']);
142  }
143  // Reset notification array
144  $this->notificationEmailInfo = array();
145  // Reset remapped IDs
146  $this->remappedIds = array();
147  }
148 
159  public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $tcemainObj) {
160  // only process the hook if it wasn't processed
161  // by someone else before
162  if (!$recordWasDeleted) {
163  $recordWasDeleted = TRUE;
164  // For Live version, try if there is a workspace version because if so, rather "delete" that instead
165  // Look, if record is an offline version, then delete directly:
166  if ($record['pid'] != -1) {
167  if ($wsVersion = BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $id)) {
168  $record = $wsVersion;
169  $id = $record['uid'];
170  }
171  }
172  $recordVersionState = VersionState::cast($record['t3ver_state']);
173  // Look, if record is an offline version, then delete directly:
174  if ($record['pid'] == -1) {
175  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
176  // In Live workspace, delete any. In other workspaces there must be match.
177  if ($tcemainObj->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $tcemainObj->BE_USER->workspace) {
178  $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
179  // Processing can be skipped if a delete placeholder shall be swapped/published
180  // during the current request. Thus it will be deleted later on...
181  $liveRecordVersionState = VersionState::cast($liveRec['t3ver_state']);
182  if ($recordVersionState->equals(VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
183  && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action'])
184  && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
185  && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
186  && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
187  ) {
188  return NULL;
189  }
190 
191  if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(VersionState::DEFAULT_STATE)) {
192  // Change normal versioned record to delete placeholder
193  // Happens when an edited record is deleted
194  $updateFields = array(
195  't3ver_label' => 'DELETED!',
196  't3ver_state' => 2,
197  );
198  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . $id, $updateFields);
199  // Delete localization overlays:
200  $tcemainObj->deleteL10nOverlayRecords($table, $id);
201 
202  } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
203  // Delete those in WS 0 + if their live records state was not "Placeholder".
204  $tcemainObj->deleteEl($table, $id);
205  // Delete move-placeholder if current version record is a move-to-pointer
206  if ($recordVersionState->equals(VersionState::MOVE_POINTER)) {
207  $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid', $record['t3ver_wsid']);
208  if (!empty($movePlaceholder)) {
209  $tcemainObj->deleteEl($table, $movePlaceholder['uid']);
210  }
211  }
212  } else {
213  // If live record was placeholder (new/deleted), rather clear
214  // it from workspace (because it clears both version and placeholder).
215  $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
216  }
217  } else {
218  $tcemainObj->newlog('Tried to delete record from another workspace', 1);
219  }
220  } else {
221  $tcemainObj->newlog('Versioning not enabled for record with PID = -1!', 2);
222  }
223  } elseif ($res = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
224  // Look, if record is "online" or in a versionized branch, then delete directly.
225  if ($res > 0) {
226  $tcemainObj->deleteEl($table, $id);
227  } else {
228  $tcemainObj->newlog('Stage of root point did not allow for deletion', 1);
229  }
230  } elseif ($recordVersionState->equals(VersionState::MOVE_PLACEHOLDER)) {
231  // Placeholders for moving operations are deletable directly.
232  // Get record which its a placeholder for and reset the t3ver_state of that:
233  if ($wsRec = BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
234  // Clear the state flag of the workspace version of the record
235  // Setting placeholder state value for version (so it can know it is currently a new version...)
236  $updateFields = array(
237  't3ver_state' => (string)new VersionState(VersionState::DEFAULT_STATE)
238  );
239  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$wsRec['uid'], $updateFields);
240  }
241  $tcemainObj->deleteEl($table, $id);
242  } else {
243  // Otherwise, try to delete by versioning:
244  $copyMappingArray = $tcemainObj->copyMappingArray;
245  $tcemainObj->versionizeRecord($table, $id, 'DELETED!', TRUE);
246  // Determine newly created versions:
247  // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
248  $versionizedElements = GeneralUtility::arrayDiffAssocRecursive($tcemainObj->copyMappingArray, $copyMappingArray);
249  // Delete localization overlays:
250  foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
251  foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
252  $tcemainObj->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
253  }
254  }
255  }
256  }
257  }
258 
273  public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $tcemainObj) {
274  // Only do something in Draft workspace
275  if ($tcemainObj->BE_USER->workspace !== 0) {
276  if ($destPid < 0) {
277  // Fetch move placeholder, since it might point to a new page in the current workspace
278  $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid,pid');
279  if ($movePlaceHolder !== FALSE) {
280  $resolvedPid = $movePlaceHolder['pid'];
281  }
282  }
283  $recordWasMoved = TRUE;
284  $moveRecVersionState = VersionState::cast($moveRec['t3ver_state']);
285  // Get workspace version of the source record, if any:
286  $WSversion = BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
287  // Handle move-placeholders if the current record is not one already
288  if (
290  && !$moveRecVersionState->equals(VersionState::MOVE_PLACEHOLDER)
291  ) {
292  // Create version of record first, if it does not exist
293  if (empty($WSversion['uid'])) {
294  $tcemainObj->versionizeRecord($table, $uid, 'MovePointer');
295  $WSversion = BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
296  $this->moveRecord_processFields($tcemainObj, $resolvedPid, $table, $uid);
297  // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
298  } elseif ($tcemainObj->isRecordCopied($table, $uid) && (int)$tcemainObj->copyMappingArray[$table][$uid] === (int)$WSversion['uid']) {
299  $this->moveRecord_processFields($tcemainObj, $resolvedPid, $table, $uid);
300  }
301  }
302  // Check workspace permissions:
303  $workspaceAccessBlocked = array();
304  // Element was in "New/Deleted/Moved" so it can be moved...
305  $recIsNewVersion = $moveRecVersionState->indicatesPlaceholder();
306  $destRes = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
307  $canMoveRecord = ($recIsNewVersion || BackendUtility::isTableMovePlaceholderAware($table));
308  // Workspace source check:
309  if (!$recIsNewVersion) {
310  $errorCode = $tcemainObj->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
311  if ($errorCode) {
312  $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
313  } elseif (!$canMoveRecord && $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
314  $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
315  }
316  }
317  // Workspace destination check:
318  // All records can be inserted if $destRes is greater than zero.
319  // Only new versions can be inserted if $destRes is FALSE.
320  // NO RECORDS can be inserted if $destRes is negative which indicates a stage
321  // not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
322  if (!($destRes > 0 || $canMoveRecord && !$destRes)) {
323  $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
324  } elseif ($destRes == 1 && $WSversion['uid']) {
325  $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
326  }
327  if (!count($workspaceAccessBlocked)) {
328  // If the move operation is done on a versioned record, which is
329  // NOT new/deleted placeholder and versioningWS is in version 2, then...
330  if ($WSversion['uid'] && !$recIsNewVersion && BackendUtility::isTableMovePlaceholderAware($table)) {
331  $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $tcemainObj);
332  } else {
333  // moving not needed, just behave like in live workspace
334  $recordWasMoved = FALSE;
335  }
336  } else {
337  $tcemainObj->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
338  }
339  }
340  }
341 
351  protected function moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid) {
352  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
353  if (empty($versionedRecord)) {
354  return;
355  }
356  foreach ($versionedRecord as $field => $value) {
357  if (empty($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
358  continue;
359  }
361  $dataHandler, $resolvedPageId,
362  $table, $uid, $field, $value,
363  $GLOBALS['TCA'][$table]['columns'][$field]['config']
364  );
365  }
366  }
367 
380  protected function moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration) {
381  $inlineFieldType = $dataHandler->getInlineFieldType($configuration);
382  $inlineProcessing = (
383  ($inlineFieldType === 'list' || $inlineFieldType === 'field')
384  && BackendUtility::isTableMovePlaceholderAware($configuration['foreign_table'])
385  && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
386  );
387 
388  if ($inlineProcessing) {
389  if ($table === 'pages') {
390  // If the inline elements are related to a page record,
391  // make sure they reside at that page and not at its parent
392  $resolvedPageId = $uid;
393  }
394 
395  $dbAnalysis = $this->createRelationHandlerInstance();
396  $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
397 
398  // Moving records to a positive destination will insert each
399  // record at the beginning, thus the order is reversed here:
400  foreach ($dbAnalysis->itemArray as $item) {
401  $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
402  if (empty($versionedRecord) || VersionState::cast($versionedRecord['t3ver_state'])->indicatesPlaceholder()) {
403  continue;
404  }
405  $dataHandler->moveRecord($item['table'], $item['id'], $resolvedPageId);
406  }
407  }
408  }
409 
410  /****************************
411  ***** Notifications ******
412  ****************************/
425  protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, DataHandler $tcemainObj, array $notificationAlternativeRecipients = array()) {
426  $workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']);
427  // So, if $id is not set, then $table is taken to be the complete element name!
428  $elementName = $id ? $table . ':' . $id : $table;
429  if (is_array($workspaceRec)) {
430  // Get the new stage title from workspaces library, if workspaces extension is installed
431  if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
432  $stageService = GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
433  $newStage = $stageService->getStageTitle((int)$stageId);
434  } else {
435  // TODO: CONSTANTS SHOULD BE USED - tx_service_workspace_workspaces
436  // TODO: use localized labels
437  // Compile label:
438  switch ((int)$stageId) {
439  case 1:
440  $newStage = 'Ready for review';
441  break;
442  case 10:
443  $newStage = 'Ready for publishing';
444  break;
445  case -1:
446  $newStage = 'Element was rejected!';
447  break;
448  case 0:
449  $newStage = 'Rejected element was noticed and edited';
450  break;
451  default:
452  $newStage = 'Unknown state change!?';
453  }
454  }
455  if (count($notificationAlternativeRecipients) == 0) {
456  // Compile list of recipients:
457  $emails = array();
458  switch ((int)$stat['stagechg_notification']) {
459  case 1:
460  switch ((int)$stageId) {
461  case 1:
462  $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
463  break;
464  case 10:
465  $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
466  break;
467  case -1:
468  // List of elements to reject:
469  $allElements = explode(',', $elementName);
470  // Traverse them, and find the history of each
471  foreach ($allElements as $elRef) {
472  list($eTable, $eUid) = explode(':', $elRef);
473  $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('log_data,tstamp,userid', 'sys_log', 'action=6 and details_nr=30
474  AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($eTable, 'sys_log') . '
475  AND recuid=' . (int)$eUid, '', 'uid DESC');
476  // Find all implicated since the last stage-raise from editing to review:
477  foreach ($rows as $dat) {
478  $data = unserialize($dat['log_data']);
479  $emails = GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($dat['userid'], TRUE));
480  if ($data['stage'] == 1) {
481  break;
482  }
483  }
484  }
485  break;
486  case 0:
487  $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
488  break;
489  default:
490  $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
491  }
492  break;
493  case 10:
494  $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
495  $emails = GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']));
496  $emails = GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['members']));
497  break;
498  default:
499  // Do nothing
500  }
501  } else {
502  $emails = $notificationAlternativeRecipients;
503  }
504  // prepare and then send the emails
505  if (count($emails)) {
506  // Path to record is found:
507  list($elementTable, $elementUid) = explode(':', $elementName);
508  $elementUid = (int)$elementUid;
509  $elementRecord = BackendUtility::getRecord($elementTable, $elementUid);
510  $recordTitle = BackendUtility::getRecordTitle($elementTable, $elementRecord);
511  if ($elementTable == 'pages') {
512  $pageUid = $elementUid;
513  } else {
514  BackendUtility::fixVersioningPid($elementTable, $elementRecord);
515  $pageUid = ($elementUid = $elementRecord['pid']);
516  }
517  // fetch the TSconfig settings for the email
518  // old way, options are TCEMAIN.notificationEmail_body/subject
519  $TCEmainTSConfig = $tcemainObj->getTCEMAIN_TSconfig($pageUid);
520  // new way, options are
521  // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
522  // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
523  $pageTsConfig = BackendUtility::getPagesTSconfig($pageUid);
524  $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
525  $markers = array(
526  '###RECORD_TITLE###' => $recordTitle,
527  '###RECORD_PATH###' => BackendUtility::getRecordPath($elementUid, '', 20),
528  '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
529  '###SITE_URL###' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
530  '###WORKSPACE_TITLE###' => $workspaceRec['title'],
531  '###WORKSPACE_UID###' => $workspaceRec['uid'],
532  '###ELEMENT_NAME###' => $elementName,
533  '###NEXT_STAGE###' => $newStage,
534  '###COMMENT###' => $comment,
535  // See: #30212 - keep both markers for compatibility
536  '###USER_REALNAME###' => $tcemainObj->BE_USER->user['realName'],
537  '###USER_FULLNAME###' => $tcemainObj->BE_USER->user['realName'],
538  '###USER_USERNAME###' => $tcemainObj->BE_USER->user['username']
539  );
540  // add marker for preview links if workspace extension is loaded
541  if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
542  $this->workspaceService = GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
543  // only generate the link if the marker is in the template - prevents database from getting to much entries
544  if (GeneralUtility::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
545  $tempEmailMessage = $GLOBALS['LANG']->sL($emailConfig['message']);
546  } else {
547  $tempEmailMessage = $emailConfig['message'];
548  }
549  if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== FALSE) {
550  $markers['###PREVIEW_LINK###'] = $this->workspaceService->generateWorkspacePreviewLink($elementUid);
551  }
552  unset($tempEmailMessage);
553  $markers['###SPLITTED_PREVIEW_LINK###'] = $this->workspaceService->generateWorkspaceSplittedPreviewLink($elementUid, TRUE);
554  }
555  // Hook for preprocessing of the content for formmails:
556  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'])) {
557  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] as $_classRef) {
558  $_procObj =& GeneralUtility::getUserObj($_classRef);
559  $markers = $_procObj->postModifyMarkers($markers, $this);
560  }
561  }
562  // send an email to each individual user, to ensure the
563  // multilanguage version of the email
564  $emailRecipients = array();
565  // an array of language objects that are needed
566  // for emails with different languages
567  $languageObjects = array(
568  $GLOBALS['LANG']->lang => $GLOBALS['LANG']
569  );
570  // loop through each recipient and send the email
571  foreach ($emails as $recipientData) {
572  // don't send an email twice
573  if (isset($emailRecipients[$recipientData['email']])) {
574  continue;
575  }
576  $emailSubject = $emailConfig['subject'];
577  $emailMessage = $emailConfig['message'];
578  $emailRecipients[$recipientData['email']] = $recipientData['email'];
579  // check if the email needs to be localized
580  // in the users' language
581  if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:') || GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
582  $recipientLanguage = $recipientData['lang'] ? $recipientData['lang'] : 'default';
583  if (!isset($languageObjects[$recipientLanguage])) {
584  // a LANG object in this language hasn't been
585  // instantiated yet, so this is done here
587  $languageObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Lang\\LanguageService');
588  $languageObject->init($recipientLanguage);
589  $languageObjects[$recipientLanguage] = $languageObject;
590  } else {
591  $languageObject = $languageObjects[$recipientLanguage];
592  }
593  if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:')) {
594  $emailSubject = $languageObject->sL($emailSubject);
595  }
596  if (GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
597  $emailMessage = $languageObject->sL($emailMessage);
598  }
599  }
600  $emailSubject = \TYPO3\CMS\Core\Html\HtmlParser::substituteMarkerArray($emailSubject, $markers, '', TRUE, TRUE);
601  $emailMessage = \TYPO3\CMS\Core\Html\HtmlParser::substituteMarkerArray($emailMessage, $markers, '', TRUE, TRUE);
602  // Send an email to the recipient
604  $mail = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');
605  if (!empty($recipientData['realName'])) {
606  $recipient = array($recipientData['email'] => $recipientData['realName']);
607  } else {
608  $recipient = $recipientData['email'];
609  }
610  $mail->setTo($recipient)
611  ->setSubject($emailSubject)
612  ->setFrom(\TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom())
613  ->setBody($emailMessage);
614  $mail->send();
615  }
616  $emailRecipients = implode(',', $emailRecipients);
617  $tcemainObj->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id);
618  }
619  }
620  }
621 
630  protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = FALSE) {
631  $users = GeneralUtility::trimExplode(',', $listOfUsers, TRUE);
632  $emails = array();
633  foreach ($users as $userIdent) {
634  if ($noTablePrefix) {
635  $id = (int)$userIdent;
636  } else {
637  list($table, $id) = GeneralUtility::revExplode('_', $userIdent, 2);
638  }
639  if ($table === 'be_users' || $noTablePrefix) {
640  if ($userRecord = BackendUtility::getRecord('be_users', $id, 'uid,email,lang,realName', BackendUtility::BEenableFields('be_users'))) {
641  if (strlen(trim($userRecord['email']))) {
642  $emails[$id] = $userRecord;
643  }
644  }
645  }
646  }
647  return $emails;
648  }
649 
650  /****************************
651  ***** Stage Changes ******
652  ****************************/
665  protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = FALSE, DataHandler $tcemainObj, array $notificationAlternativeRecipients = array()) {
666  if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
667  $tcemainObj->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
668  } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
669  $record = BackendUtility::getRecord($table, $id);
670  $stat = $tcemainObj->BE_USER->checkWorkspace($record['t3ver_wsid']);
671  // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
672  if ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
673  // Set stage of record:
674  $updateData = array(
675  't3ver_stage' => $stageId
676  );
677  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$id, $updateData);
678  $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
679  // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
680  $tcemainObj->log($table, $id, 6, 0, 0, 'Stage raised...', 30, array('comment' => $comment, 'stage' => $stageId));
681  if ((int)$stat['stagechg_notification'] > 0) {
683  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = array($stat, $stageId, $comment);
684  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
685  $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
686  } else {
687  $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients);
688  }
689  }
690  } else {
691  $tcemainObj->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
692  }
693  } else {
694  $tcemainObj->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
695  }
696  }
697 
698  /*****************************
699  ***** CMD versioning ******
700  *****************************/
711  protected function versionizePages($uid, $label, $versionizeTree, DataHandler $tcemainObj) {
712  $uid = (int)$uid;
713  // returns the branch
714  $brExist = $tcemainObj->doesBranchExist('', $uid, $tcemainObj->pMap['show'], 1);
715  // Checks if we had permissions
716  if ($brExist != -1) {
717  // Make list of tables that should come along with a new version of the page:
718  $verTablesArray = array();
719  $allTables = array_keys($GLOBALS['TCA']);
720  foreach ($allTables as $tableName) {
721  if ($tableName != 'pages' && ($versionizeTree > 0 || $GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages'])) {
722  $verTablesArray[] = $tableName;
723  }
724  }
725  // Remove the possible inline child tables from the tables to be versioniozed automatically:
726  $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages'));
727  // Begin to copy pages if we're allowed to:
728  if ($versionizeTree === -1) {
729  // Versionize this page:
730  $theNewRootID = $tcemainObj->versionizeRecord('pages', $uid, $label, FALSE, $versionizeTree);
731  if ($theNewRootID) {
732  $this->rawCopyPageContent($uid, $theNewRootID, $verTablesArray, $tcemainObj);
733  // If we're going to copy recursively...:
734  if ($versionizeTree > 0) {
735  // Get ALL subpages to copy (read permissions respected - they should NOT be...):
736  $CPtable = $tcemainObj->int_pageTreeInfo(array(), $uid, (int)$versionizeTree, $theNewRootID);
737  // Now copying the subpages
738  foreach ($CPtable as $thePageUid => $thePagePid) {
739  $newPid = $tcemainObj->copyMappingArray['pages'][$thePagePid];
740  if (isset($newPid)) {
741  $theNewRootID = $tcemainObj->copyRecord_raw('pages', $thePageUid, $newPid);
742  $this->rawCopyPageContent($thePageUid, $theNewRootID, $verTablesArray, $tcemainObj);
743  } else {
744  $tcemainObj->newlog('Something went wrong during copying branch (for versioning)', 1);
745  break;
746  }
747  }
748  }
749  } else {
750  $tcemainObj->newlog('The root version could not be created!', 1);
751  }
752  } else {
753  $tcemainObj->newlog('Versioning type "' . $versionizeTree . '" was not allowed in workspace', 1);
754  }
755  } else {
756  $tcemainObj->newlog('Could not read all subpages to versionize.', 1);
757  }
758  }
759 
774  protected function version_swap($table, $id, $swapWith, $swapIntoWS = 0, DataHandler $tcemainObj, $comment = '', $notificationEmailInfo = FALSE, $notificationAlternativeRecipients = array()) {
775  // First, check if we may actually edit the online record
776  if ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
777  // Select the two versions:
778  $curVersion = BackendUtility::getRecord($table, $id, '*');
779  $swapVersion = BackendUtility::getRecord($table, $swapWith, '*');
780  $movePlh = array();
781  $movePlhID = 0;
782  if (is_array($curVersion) && is_array($swapVersion)) {
783  if ($tcemainObj->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
784  $wsAccess = $tcemainObj->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
785  if ($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10) {
786  if ($tcemainObj->doesRecordExist($table, $swapWith, 'show') && $tcemainObj->checkRecordUpdateAccess($table, $swapWith)) {
787  if (!$swapIntoWS || $tcemainObj->BE_USER->workspaceSwapAccess()) {
788  // Check if the swapWith record really IS a version of the original!
789  if (((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0) && (int)$swapVersion['t3ver_oid'] === (int)$id) {
790  // Lock file name:
791  $lockFileName = PATH_site . 'typo3temp/swap_locking/' . $table . ':' . $id . '.ser';
792  if (!@is_file($lockFileName)) {
793  // Write lock-file:
794  GeneralUtility::writeFileToTypo3tempDir($lockFileName, serialize(array(
795  'tstamp' => $GLOBALS['EXEC_TIME'],
796  'user' => $tcemainObj->BE_USER->user['username'],
797  'curVersion' => $curVersion,
798  'swapVersion' => $swapVersion
799  )));
800  // Find fields to keep
801  $keepFields = $this->getUniqueFields($table);
802  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
803  $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
804  }
805  // l10n-fields must be kept otherwise the localization
806  // will be lost during the publishing
807  if (!isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
808  $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
809  }
810  // Swap "keepfields"
811  foreach ($keepFields as $fN) {
812  $tmp = $swapVersion[$fN];
813  $swapVersion[$fN] = $curVersion[$fN];
814  $curVersion[$fN] = $tmp;
815  }
816  // Preserve states:
817  $t3ver_state = array();
818  $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
819  $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
820  // Modify offline version to become online:
821  $tmp_wsid = $swapVersion['t3ver_wsid'];
822  // Set pid for ONLINE
823  $swapVersion['pid'] = (int)$curVersion['pid'];
824  // We clear this because t3ver_oid only make sense for offline versions
825  // and we want to prevent unintentional misuse of this
826  // value for online records.
827  $swapVersion['t3ver_oid'] = 0;
828  // In case of swapping and the offline record has a state
829  // (like 2 or 4 for deleting or move-pointer) we set the
830  // current workspace ID so the record is not deselected
831  // in the interface by BackendUtility::versioningPlaceholderClause()
832  $swapVersion['t3ver_wsid'] = 0;
833  if ($swapIntoWS) {
834  if ($t3ver_state['swapVersion'] > 0) {
835  $swapVersion['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
836  } else {
837  $swapVersion['t3ver_wsid'] = (int)$curVersion['t3ver_wsid'];
838  }
839  }
840  $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
841  $swapVersion['t3ver_stage'] = 0;
842  if (!$swapIntoWS) {
843  $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
844  }
845  // Moving element.
846  if ((int)$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
847  // && $t3ver_state['swapVersion']==4 // Maybe we don't need this?
848  if ($plhRec = BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
849  $movePlhID = $plhRec['uid'];
850  $movePlh['pid'] = $swapVersion['pid'];
851  $swapVersion['pid'] = (int)$plhRec['pid'];
852  $curVersion['t3ver_state'] = (int)$swapVersion['t3ver_state'];
853  $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
854  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
855  // sortby is a "keepFields" which is why this will work...
856  $movePlh[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
857  $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
858  }
859  }
860  }
861  // Take care of relations in each field (e.g. IRRE):
862  if (is_array($GLOBALS['TCA'][$table]['columns'])) {
863  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
864  $this->version_swap_processFields($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $tcemainObj);
865  }
866  }
867  unset($swapVersion['uid']);
868  // Modify online version to become offline:
869  unset($curVersion['uid']);
870  // Set pid for OFFLINE
871  $curVersion['pid'] = -1;
872  $curVersion['t3ver_oid'] = (int)$id;
873  $curVersion['t3ver_wsid'] = $swapIntoWS ? (int)$tmp_wsid : 0;
874  $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
875  $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
876  // Increment lifecycle counter
877  $curVersion['t3ver_stage'] = 0;
878  if (!$swapIntoWS) {
879  $curVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
880  }
881  // Registering and swapping MM relations in current and swap records:
882  $tcemainObj->version_remapMMForVersionSwap($table, $id, $swapWith);
883  // Generating proper history data to prepare logging
884  $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
885  $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
886  // Execute swapping:
887  $sqlErrors = array();
888  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$id, $swapVersion);
889  if ($GLOBALS['TYPO3_DB']->sql_error()) {
890  $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
891  } else {
892  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$swapWith, $curVersion);
893  if ($GLOBALS['TYPO3_DB']->sql_error()) {
894  $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
895  } else {
896  unlink($lockFileName);
897  }
898  }
899  if (!count($sqlErrors)) {
900  // Register swapped ids for later remapping:
901  $this->remappedIds[$table][$id] = $swapWith;
902  $this->remappedIds[$table][$swapWith] = $id;
903  // If a moving operation took place...:
904  if ($movePlhID) {
905  // Remove, if normal publishing:
906  if (!$swapIntoWS) {
907  // For delete + completely delete!
908  $tcemainObj->deleteEl($table, $movePlhID, TRUE, TRUE);
909  } else {
910  // Otherwise update the movePlaceholder:
911  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$movePlhID, $movePlh);
912  $tcemainObj->addRemapStackRefIndex($table, $movePlhID);
913  }
914  }
915  // Checking for delete:
916  // Delete only if new/deleted placeholders are there.
917  if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) {
918  // Force delete
919  $tcemainObj->deleteEl($table, $id, TRUE);
920  }
921  $tcemainObj->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
922  // Update reference index of the live record:
923  $tcemainObj->addRemapStackRefIndex($table, $id);
924  // Set log entry for live record:
925  $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion);
926  if ($propArr['_ORIG_pid'] == -1) {
927  $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xlf:version_swap.offline_record_updated');
928  } else {
929  $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xlf:version_swap.online_record_updated');
930  }
931  $theLogId = $tcemainObj->log($table, $id, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
932  $tcemainObj->setHistory($table, $id, $theLogId);
933  // Update reference index of the offline record:
934  $tcemainObj->addRemapStackRefIndex($table, $swapWith);
935  // Set log entry for offline record:
936  $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion);
937  if ($propArr['_ORIG_pid'] == -1) {
938  $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xlf:version_swap.offline_record_updated');
939  } else {
940  $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xlf:version_swap.online_record_updated');
941  }
942  $theLogId = $tcemainObj->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $swapWith), $propArr['event_pid']);
943  $tcemainObj->setHistory($table, $swapWith, $theLogId);
944 
945  $stageId = -20; // \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID;
947  $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
948  $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = array($wsAccess, $stageId, $comment);
949  $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = $table . ':' . $id;
950  $this->notificationEmailInfo[$notificationEmailInfoKey]['alternativeRecipients'] = $notificationAlternativeRecipients;
951  } else {
952  $this->notifyStageChange($wsAccess, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients);
953  }
954  // Write to log with stageId -20
955  $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
956  $tcemainObj->log($table, $id, 6, 0, 0, 'Published', 30, array('comment' => $comment, 'stage' => $stageId));
957 
958  // Clear cache:
959  $tcemainObj->registerRecordIdForPageCacheClearing($table, $id);
960  // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
961  if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
962  // For delete + completely delete!
963  $tcemainObj->deleteEl($table, $swapWith, TRUE, TRUE);
964  }
965 
966  //Update reference index for live workspace too:
968  $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
969  $refIndexObj->setWorkspaceId(0);
970  $refIndexObj->updateRefIndexTable($table, $id);
971  $refIndexObj->updateRefIndexTable($table, $swapWith);
972  } else {
973  $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
974  }
975  } else {
976  $tcemainObj->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);
977  }
978  } else {
979  $tcemainObj->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);
980  }
981  } else {
982  $tcemainObj->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
983  }
984  } else {
985  $tcemainObj->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
986  }
987  } else {
988  $tcemainObj->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
989  }
990  } else {
991  $tcemainObj->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
992  }
993  } else {
994  $tcemainObj->newlog('Error: Either online or swap version could not be selected!', 2);
995  }
996  } else {
997  $tcemainObj->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
998  }
999  }
1000 
1009  public function writeRemappedForeignField(\TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis, array $configuration, $parentId) {
1010  foreach ($dbAnalysis->itemArray as &$item) {
1011  if (isset($this->remappedIds[$item['table']][$item['id']])) {
1012  $item['id'] = $this->remappedIds[$item['table']][$item['id']];
1013  }
1014  }
1015  $dbAnalysis->writeForeignField($configuration, $parentId);
1016  }
1017 
1030  protected function version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler) {
1031  $inlineType = $dataHandler->getInlineFieldType($configuration);
1032  if ($inlineType !== 'field') {
1033  return;
1034  }
1035  $foreignTable = $configuration['foreign_table'];
1036  // Read relations that point to the current record (e.g. live record):
1037  $liveRelations = $this->createRelationHandlerInstance();
1038  $liveRelations->setWorkspaceId(0);
1039  $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
1040  // Read relations that point to the record to be swapped with e.g. draft record):
1041  $versionRelations = $this->createRelationHandlerInstance();
1042  $versionRelations->setUseLiveReferenceIds(FALSE);
1043  $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
1044  // Update relations for both (workspace/versioning) sites:
1045  if (count($liveRelations->itemArray)) {
1046  $dataHandler->addRemapAction(
1047  $tableName, $liveData['uid'],
1048  array($this, 'updateInlineForeignFieldSorting'),
1049  array($tableName, $liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace)
1050  );
1051  }
1052  if (count($versionRelations->itemArray)) {
1053  $dataHandler->addRemapAction(
1054  $tableName, $liveData['uid'],
1055  array($this, 'updateInlineForeignFieldSorting'),
1056  array($tableName, $liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0)
1057  );
1058  }
1059  }
1060 
1079  public function updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId) {
1080  $remappedIds = array();
1081  // Use remapped ids (live id <-> version id)
1082  foreach ($foreignIds as $foreignId) {
1083  if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
1084  $remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
1085  } else {
1086  $remappedIds[] = $foreignId;
1087  }
1088  }
1089 
1090  $relationHandler = $this->createRelationHandlerInstance();
1091  $relationHandler->setWorkspaceId($targetWorkspaceId);
1092  $relationHandler->setUseLiveReferenceIds(FALSE);
1093  $relationHandler->start(implode(',', $remappedIds), $foreignTableName);
1094  $relationHandler->processDeletePlaceholder();
1095  $relationHandler->writeForeignField($configuration, $parentId);
1096  }
1097 
1107  protected function version_clearWSID($table, $id, $flush = FALSE, DataHandler $tcemainObj) {
1108  if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
1109  $tcemainObj->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
1110  } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
1111  if ($liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state')) {
1112  // Clear workspace ID:
1113  $updateData = array(
1114  't3ver_wsid' => 0,
1115  't3ver_tstamp' => $GLOBALS['EXEC_TIME']
1116  );
1117  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$id, $updateData);
1118  // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
1119  if (
1120  VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
1121  || VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
1122  ) {
1123  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$liveRec['uid'], $updateData);
1124  // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
1125  $tcemainObj->deleteEl($table, $liveRec['uid'], TRUE);
1126  }
1127  // If "deleted" flag is set for the version that got released
1128  // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
1129  $wsRec = BackendUtility::getRecord($table, $id);
1130  if (
1131  $flush
1132  || (
1133  VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
1134  || VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
1135  )
1136  ) {
1137  $tcemainObj->deleteEl($table, $id, TRUE, TRUE);
1138  }
1139  // Remove the move-placeholder if found for live record.
1140  if ((int)$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
1141  if ($plhRec = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
1142  $tcemainObj->deleteEl($table, $plhRec['uid'], TRUE, TRUE);
1143  }
1144  }
1145  }
1146  } else {
1147  $tcemainObj->newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
1148  }
1149  }
1150 
1151  /*******************************
1152  ***** helper functions ******
1153  *******************************/
1165  protected function rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, DataHandler $tcemainObj) {
1166  if ($newPageId) {
1167  foreach ($copyTablesArray as $table) {
1168  // all records under the page is copied.
1169  if ($table && is_array($GLOBALS['TCA'][$table]) && $table !== 'pages') {
1170  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid=' . (int)$oldPageId . $tcemainObj->deleteClause($table));
1171  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
1172  // Check, if this record has already been copied by a parent record as relation:
1173  if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) {
1174  // Copying each of the underlying records (method RAW)
1175  $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId);
1176  }
1177  }
1178  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
1179  }
1180  }
1181  }
1182  }
1183 
1192  public function findPageElementsForVersionSwap($table, $id, $offlineId) {
1193  $rec = BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
1194  $workspaceId = $rec['t3ver_wsid'];
1195  $elementData = array();
1196  if ($workspaceId != 0) {
1197  // Get page UID for LIVE and workspace
1198  if ($table != 'pages') {
1199  $rec = BackendUtility::getRecord($table, $id, 'pid');
1200  $pageId = $rec['pid'];
1201  $rec = BackendUtility::getRecord('pages', $pageId);
1202  BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1203  $offlinePageId = $rec['_ORIG_uid'];
1204  } else {
1205  $pageId = $id;
1206  $offlinePageId = $offlineId;
1207  }
1208  // Traversing all tables supporting versioning:
1209  foreach ($GLOBALS['TCA'] as $table => $cfg) {
1210  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1211  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('A.uid AS offlineUid, B.uid AS uid', $table . ' A,' . $table . ' B', 'A.pid=-1 AND B.pid=' . $pageId . ' AND A.t3ver_wsid=' . $workspaceId . ' AND B.uid=A.t3ver_oid' . BackendUtility::deleteClause($table, 'A') . BackendUtility::deleteClause($table, 'B'));
1212  while (FALSE != ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1213  $elementData[$table][] = array($row[1], $row[0]);
1214  }
1215  $GLOBALS['TYPO3_DB']->sql_free_result($res);
1216  }
1217  }
1218  if ($offlinePageId && $offlinePageId != $pageId) {
1219  $elementData['pages'][] = array($pageId, $offlinePageId);
1220  }
1221  }
1222  return $elementData;
1223  }
1224 
1233  public function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList) {
1234  if ($workspaceId != 0) {
1235  // Traversing all tables supporting versioning:
1236  foreach ($GLOBALS['TCA'] as $table => $cfg) {
1237  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1238  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT A.uid', $table . ' A,' . $table . ' B', 'A.pid=-1' . ' AND A.t3ver_wsid=' . $workspaceId . ' AND B.pid IN (' . implode(',', $pageIdList) . ') AND A.t3ver_oid=B.uid' . BackendUtility::deleteClause($table, 'A') . BackendUtility::deleteClause($table, 'B'));
1239  while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1240  $elementList[$table][] = $row[0];
1241  }
1242  $GLOBALS['TYPO3_DB']->sql_free_result($res);
1243  if (is_array($elementList[$table])) {
1244  // Yes, it is possible to get non-unique array even with DISTINCT above!
1245  // It happens because several UIDs are passed in the array already.
1246  $elementList[$table] = array_unique($elementList[$table]);
1247  }
1248  }
1249  }
1250  }
1251  }
1252 
1263  public function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList) {
1264  if ($workspaceId != 0) {
1265  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT B.pid', $table . ' A,' . $table . ' B', 'A.pid=-1' . ' AND A.t3ver_wsid=' . $workspaceId . ' AND A.uid IN (' . implode(',', $idList) . ') AND A.t3ver_oid=B.uid' . BackendUtility::deleteClause($table, 'A') . BackendUtility::deleteClause($table, 'B'));
1266  while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1267  $pageIdList[] = $row[0];
1268  // Find ws version
1269  // Note: cannot use BackendUtility::getRecordWSOL()
1270  // here because it does not accept workspace id!
1271  $rec = BackendUtility::getRecord('pages', $row[0]);
1272  BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1273  if ($rec['_ORIG_uid']) {
1274  $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
1275  }
1276  }
1277  $GLOBALS['TYPO3_DB']->sql_free_result($res);
1278  // The line below is necessary even with DISTINCT
1279  // because several elements can be passed by caller
1280  $pageIdList = array_unique($pageIdList);
1281  }
1282  }
1283 
1290  public function findRealPageIds(array &$idList) {
1291  foreach ($idList as $key => $id) {
1292  $rec = BackendUtility::getRecord('pages', $id, 't3ver_oid');
1293  if ($rec['t3ver_oid'] > 0) {
1294  $idList[$key] = $rec['t3ver_oid'];
1295  }
1296  }
1297  }
1298 
1313  protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, DataHandler $tcemainObj) {
1314  // If a record gets moved after a record that already has a placeholder record
1315  // then the new placeholder record needs to be after the existing one
1316  $originalRecordDestinationPid = $destPid;
1317  if ($destPid < 0) {
1318  $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
1319  if ($movePlaceHolder !== FALSE) {
1320  $destPid = -$movePlaceHolder['uid'];
1321  }
1322  }
1323  if ($plh = BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
1324  // If already a placeholder exists, move it:
1325  $tcemainObj->moveRecord_raw($table, $plh['uid'], $destPid);
1326  } else {
1327  // First, we create a placeholder record in the Live workspace that
1328  // represents the position to where the record is eventually moved to.
1329  $newVersion_placeholderFieldArray = array();
1330 
1331  // Use property for move placeholders if set (since TYPO3 CMS 6.2)
1332  if (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'])) {
1333  $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'];
1334  // Fallback to property for new placeholder (existed long time before TYPO3 CMS 6.2)
1335  } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'])) {
1336  $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'];
1337  }
1338 
1339  // Set values from the versioned record to the move placeholder
1340  if (!empty($shadowColumnsForMovePlaceholder)) {
1341  $versionedRecord = BackendUtility::getRecord($table, $wsUid);
1342  $shadowColumns = GeneralUtility::trimExplode(',', $shadowColumnsForMovePlaceholder, TRUE);
1343  foreach ($shadowColumns as $shadowColumn) {
1344  if (isset($versionedRecord[$shadowColumn])) {
1345  $newVersion_placeholderFieldArray[$shadowColumn] = $versionedRecord[$shadowColumn];
1346  }
1347  }
1348  }
1349 
1350  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1351  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1352  }
1353  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1354  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $tcemainObj->userid;
1355  }
1356  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1357  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1358  }
1359  if ($table == 'pages') {
1360  // Copy page access settings from original page to placeholder
1361  $perms_clause = $tcemainObj->BE_USER->getPagePermsClause(1);
1362  $access = BackendUtility::readPageAccess($uid, $perms_clause);
1363  $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
1364  $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
1365  $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
1366  $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
1367  $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
1368  }
1369  $newVersion_placeholderFieldArray['t3ver_label'] = 'MovePlaceholder #' . $uid;
1370  $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
1371  // Setting placeholder state value for temporary record
1372  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(VersionState::MOVE_PLACEHOLDER);
1373  // Setting workspace - only so display of place holders can filter out those from other workspaces.
1374  $newVersion_placeholderFieldArray['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
1375  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = $tcemainObj->getPlaceholderTitleForTableLabel($table, 'MOVE-TO PLACEHOLDER for #' . $uid);
1376  // moving localized records requires to keep localization-settings for the placeholder too
1377  if (array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl'])) {
1378  $l10nParentRec = BackendUtility::getRecord($table, $uid);
1379  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
1380  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1381  unset($l10nParentRec);
1382  }
1383  // Initially, create at root level.
1384  $newVersion_placeholderFieldArray['pid'] = 0;
1385  $id = 'NEW_MOVE_PLH';
1386  // Saving placeholder as 'original'
1387  $tcemainObj->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE);
1388  // Move the new placeholder from temporary root-level to location:
1389  $tcemainObj->moveRecord_raw($table, $tcemainObj->substNEWwithIDs[$id], $destPid);
1390  // Move the workspace-version of the original to be the version of the move-to-placeholder:
1391  // Setting placeholder state value for version (so it can know it is currently a new version...)
1392  $updateFields = array(
1393  't3ver_state' => (string)new VersionState(VersionState::MOVE_POINTER)
1394  );
1395  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$wsUid, $updateFields);
1396  }
1397  // Check for the localizations of that element and move them as well
1398  $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
1399  }
1400 
1408  protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) {
1409  foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) {
1410  if (isset($parentFieldDefinition['config']['type'])) {
1411  $parentFieldConfiguration = $parentFieldDefinition['config'];
1412  if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) {
1413  if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) {
1414  $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable($parentFieldConfiguration['foreign_table'], array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table']));
1415  }
1416  }
1417  }
1418  }
1419  return $possibleInlineChildren;
1420  }
1421 
1428  public function getCommandMap(DataHandler $tceMain) {
1430  'TYPO3\\CMS\\Version\\DataHandler\\CommandMap',
1431  $this,
1432  $tceMain,
1433  $tceMain->cmdmap,
1434  $tceMain->BE_USER->workspace
1435  );
1436  }
1437 
1444  protected function getUniqueFields($table) {
1445  $listArr = array();
1446  if ($GLOBALS['TCA'][$table]['columns']) {
1447  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
1448  if ($configArr['config']['type'] === 'input') {
1449  $evalCodesArray = GeneralUtility::trimExplode(',', $configArr['config']['eval'], TRUE);
1450  if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
1451  $listArr[] = $field;
1452  }
1453  }
1454  }
1455  }
1456  return $listArr;
1457  }
1458 
1462  protected function createRelationHandlerInstance() {
1463  return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
1464  }
1465 
1466 }
int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
doesBranchExist($inList, $pid, $perms, $recurse)
static readPageAccess($id, $perms_clause)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
processCmdmap_afterFinish(DataHandler $tcemainObj)
rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, DataHandler $tcemainObj)
updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
static isFirstPartOfStr($str, $partStr)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=FALSE)
versionizeRecord($table, $id, $label, $delete=FALSE)
$uid
Definition: server.php:36
static writeFileToTypo3tempDir($filepath, $content)
static getUserObj($classRef, $checkPrefix='', $silent=FALSE)
static getMovePlaceholder($table, $uid, $fields=' *', $workspace=NULL)
getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren=array())
moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
processCmdmap_beforeStart(DataHandler $tcemainObj)
static trimExplode($delim, $string, $removeEmptyValues=FALSE, $limit=0)
processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $tcemainObj)
copyRecord_raw($table, $uid, $pid, $overrideArray=array(), array $workspaceOptions=array())
addRemapAction($table, $id, array $callback, array $arguments)
static getRecordTitle($table, $row, $prep=FALSE, $forceResult=TRUE)
version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix=FALSE)
checkRecordUpdateAccess($table, $id, $data=FALSE, &$hookObjectsArr=FALSE)
findPageElementsForVersionSwap($table, $id, $offlineId)
versionizePages($uid, $label, $versionizeTree, DataHandler $tcemainObj)
compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
getPlaceholderTitleForTableLabel($table, $placeholderContent=NULL)
static substituteMarkerArray($content, $markContentArray, $wrap='', $uppercase=FALSE, $deleteUnused=FALSE)
Definition: HtmlParser.php:189
log($table, $recuid, $action, $recpid, $error, $details, $details_nr=-1, $data=array(), $event_pid=-1, $NEWid='')
version_setStage($table, $id, $stageId, $comment='', $notificationEmailInfo=FALSE, DataHandler $tcemainObj, array $notificationAlternativeRecipients=array())
findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
static array_merge(array $arr1, array $arr2)
moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $tcemainObj)
static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=FALSE)
static getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
static getLiveVersionOfRecord($table, $uid, $fields=' *')
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static revExplode($delimiter, $string, $count=0)
moveRecord_raw($table, $uid, $destPid)
version_clearWSID($table, $id, $flush=FALSE, DataHandler $tcemainObj)
static getPagesTSconfig($id, $rootLine=NULL, $returnPartArray=FALSE)
static arrayDiffAssocRecursive(array $array1, array $array2)
moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, DataHandler $tcemainObj)
moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration)
writeRemappedForeignField(\TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis, array $configuration, $parentId)
static deleteClause($table, $tableAlias='')
insertDB($table, $id, $fieldArray, $newVersion=FALSE, $suggestedUid=0, $dontSetNewIdIndex=FALSE)
moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $tcemainObj)
newlog2($message, $table, $uid, $pid=FALSE, $error=0)