ad10d8d63f0cc0c84b42454f6c5ff42732979510
[Packages/TYPO3.CMS.git] / typo3 / sysext / version / Classes / Hook / DataHandlerHook.php
1 <?php
2 namespace TYPO3\CMS\Version\Hook;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * (c) 2010-2013 Benjamin Mack (benni@typo3.org)
9 *
10 * All rights reserved
11 *
12 * This script is part of the TYPO3 project. The TYPO3 project is
13 * free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * The GNU General Public License can be found at
19 * http://www.gnu.org/copyleft/gpl.html.
20 * A copy is found in the textfile GPL.txt and important notices to the license
21 * from the author is found in LICENSE.txt distributed with these scripts.
22 *
23 *
24 * This script is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /**
32 * Contains some parts for staging, versioning and workspaces
33 * to interact with the TYPO3 Core Engine
34 */
35 class DataHandlerHook {
36
37 /**
38 * For accumulating information about workspace stages raised
39 * on elements so a single mail is sent as notification.
40 * previously called "accumulateForNotifEmail" in tcemain
41 *
42 * @var array
43 */
44 protected $notificationEmailInfo = array();
45
46 /**
47 * General comment, eg. for staging in workspaces
48 *
49 * @var string
50 */
51 protected $generalComment = '';
52
53 /**
54 * Contains remapped IDs.
55 *
56 * @var array
57 */
58 protected $remappedIds = array();
59
60 /****************************
61 ***** Cmdmap Hooks ******
62 ****************************/
63 /**
64 * hook that is called before any cmd of the commandmap is executed
65 *
66 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj reference to the main tcemain object
67 * @return void
68 */
69 public function processCmdmap_beforeStart(\TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
70 // Reset notification array
71 $this->notificationEmailInfo = array();
72 // Resolve dependencies of version/workspaces actions:
73 $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj, $tcemainObj->cmdmap)->process()->get();
74 }
75
76 /**
77 * hook that is called when no prepared command was found
78 *
79 * @param string $command the command to be executed
80 * @param string $table the table of the record
81 * @param integer $id the ID of the record
82 * @param mixed $value the value containing the data
83 * @param boolean $commandIsProcessed can be set so that other hooks or
84 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj reference to the main tcemain object
85 * @return void
86 */
87 public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
88 // custom command "version"
89 if ($command == 'version') {
90 $commandIsProcessed = TRUE;
91 $action = (string) $value['action'];
92 $comment = (isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment);
93 $notificationAlternativeRecipients = (isset($value['notificationAlternativeRecipients'])) && is_array($value['notificationAlternativeRecipients']) ? $value['notificationAlternativeRecipients'] : array();
94 switch ($action) {
95 case 'new':
96 // check if page / branch versioning is needed,
97 // or if "element" version can be used
98 $versionizeTree = -1;
99 if (isset($value['treeLevels'])) {
100 $versionizeTree = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value['treeLevels'], -1, 100);
101 }
102 if ($table == 'pages' && $versionizeTree >= 0) {
103 $this->versionizePages($id, $value['label'], $versionizeTree, $tcemainObj);
104 } else {
105 $tcemainObj->versionizeRecord($table, $id, $value['label']);
106 }
107 break;
108 case 'swap':
109 $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'],
110 $tcemainObj,
111 $comment,
112 TRUE,
113 $notificationAlternativeRecipients
114 );
115 break;
116 case 'clearWSID':
117 $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
118 break;
119 case 'flush':
120 $this->version_clearWSID($table, $id, TRUE, $tcemainObj);
121 break;
122 case 'setStage':
123 $elementIds = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $id, TRUE);
124 foreach ($elementIds as $elementId) {
125 $this->version_setStage($table, $elementId, $value['stageId'],
126 $comment,
127 TRUE,
128 $tcemainObj,
129 $notificationAlternativeRecipients
130 );
131 }
132 break;
133 }
134 }
135 }
136
137 /**
138 * Hook that is called after tcemain made most of its decisions.
139 *
140 * NOTE: This fixes an issue related to moving/creating initial-placeholders - if such a new page
141 * is intended to be place behind a move-placeholder tcemain handles the movement/creation,
142 * but does not respect the wsPlaceholder, which leads the new page to be located at the old location of the
143 * page where it was intended to be placed behind.
144 *
145 * @param string $command
146 * @param string $table
147 * @param int $id
148 * @param mixed $value
149 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemain
150 */
151 public function processCmdmap_postProcess($command, $table, $id, $value, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemain) {
152 if ($command === 'move') {
153 if ($value < 0) {
154 $movePlaceHolder = \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder($table, abs($value), 'uid');
155 if ($movePlaceHolder !== FALSE) {
156 $destPid = -$movePlaceHolder['uid'];
157 $tcemain->moveRecord_raw($table, $id, $destPid);
158 }
159 }
160 }
161 }
162
163 /**
164 * hook that is called AFTER all commands of the commandmap was
165 * executed
166 *
167 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj reference to the main tcemain object
168 * @return void
169 */
170 public function processCmdmap_afterFinish(\TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
171 // Empty accumulation array:
172 foreach ($this->notificationEmailInfo as $notifItem) {
173 $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $tcemainObj, $notifItem['alternativeRecipients']);
174 }
175 // Reset notification array
176 $this->notificationEmailInfo = array();
177 // Reset remapped IDs
178 $this->remappedIds = array();
179 }
180
181 /**
182 * hook that is called AFTER all commands of the commandmap was
183 * executed
184 *
185 * @param string $table the table of the record
186 * @param integer $id the ID of the record
187 * @param array $record The accordant database record
188 * @param boolean $recordWasDeleted can be set so that other hooks or
189 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj reference to the main tcemain object
190 * @return void
191 */
192 public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
193 // only process the hook if it wasn't processed
194 // by someone else before
195 if (!$recordWasDeleted) {
196 $recordWasDeleted = TRUE;
197 // For Live version, try if there is a workspace version because if so, rather "delete" that instead
198 // Look, if record is an offline version, then delete directly:
199 if ($record['pid'] != -1) {
200 if ($wsVersion = \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $id)) {
201 $record = $wsVersion;
202 $id = $record['uid'];
203 }
204 }
205 // Look, if record is an offline version, then delete directly:
206 if ($record['pid'] == -1) {
207 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
208 // In Live workspace, delete any. In other workspaces there must be match.
209 if ($tcemainObj->BE_USER->workspace == 0 || (int) $record['t3ver_wsid'] == $tcemainObj->BE_USER->workspace) {
210 $liveRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
211 // Processing can be skipped if a delete placeholder shall be swapped/published
212 // during the current request. Thus it will be deleted later on...
213 if ($record['t3ver_state'] == 2 && !empty($liveRec['uid']) && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action']) && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith']) && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap' && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id) {
214 return NULL;
215 }
216 // Delete those in WS 0 + if their live records state was not "Placeholder".
217 if ($record['t3ver_wsid'] == 0 || (int) $liveRec['t3ver_state'] <= 0) {
218 $tcemainObj->deleteEl($table, $id);
219 } else {
220 // If live record was placeholder (new/deleted), rather clear
221 // it from workspace (because it clears both version and placeholder).
222 $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
223 }
224 } else {
225 $tcemainObj->newlog('Tried to delete record from another workspace', 1);
226 }
227 } else {
228 $tcemainObj->newlog('Versioning not enabled for record with PID = -1!', 2);
229 }
230 } elseif ($res = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
231 // Look, if record is "online" or in a versionized branch, then delete directly.
232 if ($res > 0) {
233 $tcemainObj->deleteEl($table, $id);
234 } else {
235 $tcemainObj->newlog('Stage of root point did not allow for deletion', 1);
236 }
237 } elseif ((int) $record['t3ver_state'] === 3) {
238 // Placeholders for moving operations are deletable directly.
239 // Get record which its a placeholder for and reset the t3ver_state of that:
240 if ($wsRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
241 // Clear the state flag of the workspace version of the record
242 // Setting placeholder state value for version (so it can know it is currently a new version...)
243 $updateFields = array(
244 't3ver_state' => 0
245 );
246 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsRec['uid']), $updateFields);
247 }
248 $tcemainObj->deleteEl($table, $id);
249 } else {
250 // Otherwise, try to delete by versioning:
251 $copyMappingArray = $tcemainObj->copyMappingArray;
252 $tcemainObj->versionizeRecord($table, $id, 'DELETED!', TRUE);
253 // Determine newly created versions:
254 // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
255 $versionizedElements = \TYPO3\CMS\Core\Utility\GeneralUtility::arrayDiffAssocRecursive($tcemainObj->copyMappingArray, $copyMappingArray);
256 // Delete localization overlays:
257 foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
258 foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
259 $tcemainObj->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
260 }
261 }
262 }
263 }
264 }
265
266 /**
267 * Hook for \TYPO3\CMS\Core\DataHandling\DataHandler::moveRecord that cares about
268 * moving records that are *not* in the live workspace
269 *
270 * @param string $table the table of the record
271 * @param integer $id the ID of the record
272 * @param integer $destPid Position to move to: $destPid: >=0 then it points to
273 * @param array $propArr Record properties, like header and pid (includes workspace overlay)
274 * @param array $moveRec Record properties, like header and pid (without workspace overlay)
275 * @param integer $resolvedPid The final page ID of the record
276 * @param boolean $recordWasMoved can be set so that other hooks or
277 * @param $table the table
278 */
279 public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
280 // Only do something in Draft workspace
281 if ($tcemainObj->BE_USER->workspace !== 0) {
282 $recordWasMoved = TRUE;
283 // Get workspace version of the source record, if any:
284 $WSversion = \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
285 // If no version exists and versioningWS is in version 2, a new placeholder is made automatically:
286 if (!$WSversion['uid'] && (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2 && (int) $moveRec['t3ver_state'] != 3) {
287 $tcemainObj->versionizeRecord($table, $uid, 'Placeholder version for moving record');
288 $WSversion = \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
289 }
290 // Check workspace permissions:
291 $workspaceAccessBlocked = array();
292 // Element was in "New/Deleted/Moved" so it can be moved...
293 $recIsNewVersion = (int) $moveRec['t3ver_state'] > 0;
294 $destRes = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
295 $canMoveRecord = $recIsNewVersion || (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2;
296 // Workspace source check:
297 if (!$recIsNewVersion) {
298 $errorCode = $tcemainObj->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
299 if ($errorCode) {
300 $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
301 } elseif (!$canMoveRecord && $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
302 $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
303 }
304 }
305 // Workspace destination check:
306 // All records can be inserted if $destRes is greater than zero.
307 // Only new versions can be inserted if $destRes is FALSE.
308 // NO RECORDS can be inserted if $destRes is negative which indicates a stage
309 // not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
310 if (!($destRes > 0 || $canMoveRecord && !$destRes)) {
311 $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
312 } elseif ($destRes == 1 && $WSversion['uid']) {
313 $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
314 }
315 if (!count($workspaceAccessBlocked)) {
316 // If the move operation is done on a versioned record, which is
317 // NOT new/deleted placeholder and versioningWS is in version 2, then...
318 if ($WSversion['uid'] && !$recIsNewVersion && (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
319 $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $tcemainObj);
320 } else {
321 // moving not needed, just behave like in live workspace
322 $recordWasMoved = FALSE;
323 }
324 } else {
325 $tcemainObj->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
326 }
327 }
328 }
329
330 /****************************
331 ***** Notifications ******
332 ****************************/
333 /**
334 * Send an email notification to users in workspace
335 *
336 * @param array $stat Workspace access array from \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::checkWorkspace()
337 * @param integer $stageId New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected!
338 * @param string $table Table name of element (or list of element names if $id is zero)
339 * @param integer $id Record uid of element (if zero, then $table is used as reference to element(s) alone)
340 * @param string $comment User comment sent along with action
341 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
342 * @param array $notificationAlternativeRecipients List of recipients to notify instead of be_users selected by sys_workspace, list is generated by workspace extension module
343 * @return void
344 */
345 protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj, array $notificationAlternativeRecipients = array()) {
346 $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $stat['uid']);
347 // So, if $id is not set, then $table is taken to be the complete element name!
348 $elementName = $id ? $table . ':' . $id : $table;
349 if (is_array($workspaceRec)) {
350 // Get the new stage title from workspaces library, if workspaces extension is installed
351 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
352 $stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
353 $newStage = $stageService->getStageTitle((int) $stageId);
354 } else {
355 // TODO: CONSTANTS SHOULD BE USED - tx_service_workspace_workspaces
356 // TODO: use localized labels
357 // Compile label:
358 switch ((int) $stageId) {
359 case 1:
360 $newStage = 'Ready for review';
361 break;
362 case 10:
363 $newStage = 'Ready for publishing';
364 break;
365 case -1:
366 $newStage = 'Element was rejected!';
367 break;
368 case 0:
369 $newStage = 'Rejected element was noticed and edited';
370 break;
371 default:
372 $newStage = 'Unknown state change!?';
373 break;
374 }
375 }
376 if (count($notificationAlternativeRecipients) == 0) {
377 // Compile list of recipients:
378 $emails = array();
379 switch ((int) $stat['stagechg_notification']) {
380 case 1:
381 switch ((int) $stageId) {
382 case 1:
383 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
384 break;
385 case 10:
386 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
387 break;
388 case -1:
389 // List of elements to reject:
390 $allElements = explode(',', $elementName);
391 // Traverse them, and find the history of each
392 foreach ($allElements as $elRef) {
393 list($eTable, $eUid) = explode(':', $elRef);
394 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('log_data,tstamp,userid', 'sys_log', 'action=6 and details_nr=30
395 AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($eTable, 'sys_log') . '
396 AND recuid=' . intval($eUid), '', 'uid DESC');
397 // Find all implicated since the last stage-raise from editing to review:
398 foreach ($rows as $dat) {
399 $data = unserialize($dat['log_data']);
400 $emails = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($dat['userid'], TRUE));
401 if ($data['stage'] == 1) {
402 break;
403 }
404 }
405 }
406 break;
407 case 0:
408 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
409 break;
410 default:
411 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
412 break;
413 }
414 break;
415 case 10:
416 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
417 $emails = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']));
418 $emails = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['members']));
419 break;
420 }
421 } else {
422 $emails = $notificationAlternativeRecipients;
423 }
424 // prepare and then send the emails
425 if (count($emails)) {
426 // Path to record is found:
427 list($elementTable, $elementUid) = explode(':', $elementName);
428 $elementUid = intval($elementUid);
429 $elementRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($elementTable, $elementUid);
430 $recordTitle = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($elementTable, $elementRecord);
431 if ($elementTable == 'pages') {
432 $pageUid = $elementUid;
433 } else {
434 \TYPO3\CMS\Backend\Utility\BackendUtility::fixVersioningPid($elementTable, $elementRecord);
435 $pageUid = ($elementUid = $elementRecord['pid']);
436 }
437 // fetch the TSconfig settings for the email
438 // old way, options are TCEMAIN.notificationEmail_body/subject
439 $TCEmainTSConfig = $tcemainObj->getTCEMAIN_TSconfig($pageUid);
440 // new way, options are
441 // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
442 // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
443 $pageTsConfig = \TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig($pageUid);
444 $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
445 $markers = array(
446 '###RECORD_TITLE###' => $recordTitle,
447 '###RECORD_PATH###' => \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($elementUid, '', 20),
448 '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
449 '###SITE_URL###' => \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
450 '###WORKSPACE_TITLE###' => $workspaceRec['title'],
451 '###WORKSPACE_UID###' => $workspaceRec['uid'],
452 '###ELEMENT_NAME###' => $elementName,
453 '###NEXT_STAGE###' => $newStage,
454 '###COMMENT###' => $comment,
455 // See: #30212 - keep both markers for compatibility
456 '###USER_REALNAME###' => $tcemainObj->BE_USER->user['realName'],
457 '###USER_FULLNAME###' => $tcemainObj->BE_USER->user['realName'],
458 '###USER_USERNAME###' => $tcemainObj->BE_USER->user['username']
459 );
460 // add marker for preview links if workspace extension is loaded
461 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
462 $this->workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('tx_Workspaces_Service_Workspaces');
463 // only generate the link if the marker is in the template - prevents database from getting to much entries
464 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
465 $tempEmailMessage = $GLOBALS['LANG']->sL($emailConfig['message']);
466 } else {
467 $tempEmailMessage = $emailConfig['message'];
468 }
469 if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== FALSE) {
470 $markers['###PREVIEW_LINK###'] = $this->workspaceService->generateWorkspacePreviewLink($elementUid);
471 }
472 unset($tempEmailMessage);
473 $markers['###SPLITTED_PREVIEW_LINK###'] = $this->workspaceService->generateWorkspaceSplittedPreviewLink($elementUid, TRUE);
474 }
475 // Hook for preprocessing of the content for formmails:
476 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'])) {
477 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] as $_classRef) {
478 $_procObj =& \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_classRef);
479 $markers = $_procObj->postModifyMarkers($markers, $this);
480 }
481 }
482 // send an email to each individual user, to ensure the
483 // multilanguage version of the email
484 $emailHeaders = $emailConfig['additionalHeaders'];
485 $emailRecipients = array();
486 // an array of language objects that are needed
487 // for emails with different languages
488 $languageObjects = array(
489 $GLOBALS['LANG']->lang => $GLOBALS['LANG']
490 );
491 // loop through each recipient and send the email
492 foreach ($emails as $recipientData) {
493 // don't send an email twice
494 if (isset($emailRecipients[$recipientData['email']])) {
495 continue;
496 }
497 $emailSubject = $emailConfig['subject'];
498 $emailMessage = $emailConfig['message'];
499 $emailRecipients[$recipientData['email']] = $recipientData['email'];
500 // check if the email needs to be localized
501 // in the users' language
502 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:') || \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
503 $recipientLanguage = $recipientData['lang'] ? $recipientData['lang'] : 'default';
504 if (!isset($languageObjects[$recipientLanguage])) {
505 // a LANG object in this language hasn't been
506 // instantiated yet, so this is done here
507 /** @var $languageObject \TYPO3\CMS\Lang\LanguageService */
508 $languageObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Lang\\LanguageService');
509 $languageObject->init($recipientLanguage);
510 $languageObjects[$recipientLanguage] = $languageObject;
511 } else {
512 $languageObject = $languageObjects[$recipientLanguage];
513 }
514 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:')) {
515 $emailSubject = $languageObject->sL($emailSubject);
516 }
517 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
518 $emailMessage = $languageObject->sL($emailMessage);
519 }
520 }
521 $emailSubject = \TYPO3\CMS\Core\Html\HtmlParser::substituteMarkerArray($emailSubject, $markers, '', TRUE, TRUE);
522 $emailMessage = \TYPO3\CMS\Core\Html\HtmlParser::substituteMarkerArray($emailMessage, $markers, '', TRUE, TRUE);
523 // Send an email to the recipient
524 \TYPO3\CMS\Core\Utility\GeneralUtility::plainMailEncoded($recipientData['email'], $emailSubject, $emailMessage, $emailHeaders);
525 }
526 $emailRecipients = implode(',', $emailRecipients);
527 $tcemainObj->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id);
528 }
529 }
530 }
531
532 /**
533 * Return be_users that should be notified on stage change from input list.
534 * previously called notifyStageChange_getEmails() in tcemain
535 *
536 * @param string $listOfUsers List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set.
537 * @param boolean $noTablePrefix If set, the input list are integers and not strings.
538 * @return array Array of emails
539 */
540 protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = FALSE) {
541 $users = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $listOfUsers, 1);
542 $emails = array();
543 foreach ($users as $userIdent) {
544 if ($noTablePrefix) {
545 $id = intval($userIdent);
546 } else {
547 list($table, $id) = \TYPO3\CMS\Core\Utility\GeneralUtility::revExplode('_', $userIdent, 2);
548 }
549 if ($table === 'be_users' || $noTablePrefix) {
550 if ($userRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('be_users', $id, 'uid,email,lang,realName', \TYPO3\CMS\Backend\Utility\BackendUtility::BEenableFields('be_users'))) {
551 if (strlen(trim($userRecord['email']))) {
552 $emails[$id] = $userRecord;
553 }
554 }
555 }
556 }
557 return $emails;
558 }
559
560 /****************************
561 ***** Stage Changes ******
562 ****************************/
563 /**
564 * Setting stage of record
565 *
566 * @param string $table Table name
567 * @param integer $integer Record UID
568 * @param integer $stageId Stage ID to set
569 * @param string $comment Comment that goes into log
570 * @param boolean $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
571 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
572 * @param array $notificationAlternativeRecipients comma separated list of recipients to notify instead of normal be_users
573 * @return void
574 */
575 protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = FALSE, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj, array $notificationAlternativeRecipients = array()) {
576 if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
577 $tcemainObj->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
578 } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
579 $record = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $id);
580 $stat = $tcemainObj->BE_USER->checkWorkspace($record['t3ver_wsid']);
581 // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
582 if ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
583 // Set stage of record:
584 $updateData = array(
585 't3ver_stage' => $stageId
586 );
587 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
588 $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
589 // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
590 $tcemainObj->log($table, $id, 6, 0, 0, 'Stage raised...', 30, array('comment' => $comment, 'stage' => $stageId));
591 if ((int) $stat['stagechg_notification'] > 0) {
592 if ($notificationEmailInfo) {
593 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = array($stat, $stageId, $comment);
594 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
595 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
596 } else {
597 $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients);
598 }
599 }
600 } else {
601 $tcemainObj->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
602 }
603 } else {
604 $tcemainObj->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
605 }
606 }
607
608 /*****************************
609 ***** CMD versioning ******
610 *****************************/
611 /**
612 * Creates a new version of a page including content and possible subpages.
613 *
614 * @param integer $uid Page uid to create new version of.
615 * @param string $label Version label
616 * @param integer $versionizeTree Indicating "treeLevel" - "page" (0) or "branch" (>=1) ["element" type must call versionizeRecord() directly]
617 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
618 * @return void
619 * @see copyPages()
620 */
621 protected function versionizePages($uid, $label, $versionizeTree, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
622 $uid = intval($uid);
623 // returns the branch
624 $brExist = $tcemainObj->doesBranchExist('', $uid, $tcemainObj->pMap['show'], 1);
625 // Checks if we had permissions
626 if ($brExist != -1) {
627 // Make list of tables that should come along with a new version of the page:
628 $verTablesArray = array();
629 $allTables = array_keys($GLOBALS['TCA']);
630 foreach ($allTables as $tableName) {
631 if ($tableName != 'pages' && ($versionizeTree > 0 || $GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages'])) {
632 $verTablesArray[] = $tableName;
633 }
634 }
635 // Remove the possible inline child tables from the tables to be versioniozed automatically:
636 $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages'));
637 // Begin to copy pages if we're allowed to:
638 if ($versionizeTree === -1) {
639 // Versionize this page:
640 $theNewRootID = $tcemainObj->versionizeRecord('pages', $uid, $label, FALSE, $versionizeTree);
641 if ($theNewRootID) {
642 $this->rawCopyPageContent($uid, $theNewRootID, $verTablesArray, $tcemainObj);
643 // If we're going to copy recursively...:
644 if ($versionizeTree > 0) {
645 // Get ALL subpages to copy (read permissions respected - they should NOT be...):
646 $CPtable = $tcemainObj->int_pageTreeInfo(array(), $uid, intval($versionizeTree), $theNewRootID);
647 // Now copying the subpages
648 foreach ($CPtable as $thePageUid => $thePagePid) {
649 $newPid = $tcemainObj->copyMappingArray['pages'][$thePagePid];
650 if (isset($newPid)) {
651 $theNewRootID = $tcemainObj->copyRecord_raw('pages', $thePageUid, $newPid);
652 $this->rawCopyPageContent($thePageUid, $theNewRootID, $verTablesArray, $tcemainObj);
653 } else {
654 $tcemainObj->newlog('Something went wrong during copying branch (for versioning)', 1);
655 break;
656 }
657 }
658 }
659 } else {
660 $tcemainObj->newlog('The root version could not be created!', 1);
661 }
662 } else {
663 $tcemainObj->newlog('Versioning type "' . $versionizeTree . '" was not allowed in workspace', 1);
664 }
665 } else {
666 $tcemainObj->newlog('Could not read all subpages to versionize.', 1);
667 }
668 }
669
670 /**
671 * Swapping versions of a record
672 * Version from archive (future/past, called "swap version") will get the uid of the "t3ver_oid", the official element with uid = "t3ver_oid" will get the new versions old uid. PIDs are swapped also
673 *
674 * @param string $table Table name
675 * @param integer $id UID of the online record to swap
676 * @param integer $swapWith UID of the archived version to swap with!
677 * @param boolean $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace.
678 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
679 * @param string $comment Notification comment
680 * @param boolean $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
681 * @param array $notificationAlternativeRecipients comma separated list of recipients to notificate instead of normal be_users
682 * @return void
683 */
684 protected function version_swap($table, $id, $swapWith, $swapIntoWS = 0, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj, $comment = '', $notificationEmailInfo = FALSE, $notificationAlternativeRecipients = array()) {
685 // First, check if we may actually edit the online record
686 if ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
687 // Select the two versions:
688 $curVersion = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $id, '*');
689 $swapVersion = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $swapWith, '*');
690 $movePlh = array();
691 $movePlhID = 0;
692 if (is_array($curVersion) && is_array($swapVersion)) {
693 if ($tcemainObj->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
694 $wsAccess = $tcemainObj->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
695 if ($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int) $swapVersion['t3ver_stage'] === -10) {
696 if ($tcemainObj->doesRecordExist($table, $swapWith, 'show') && $tcemainObj->checkRecordUpdateAccess($table, $swapWith)) {
697 if (!$swapIntoWS || $tcemainObj->BE_USER->workspaceSwapAccess()) {
698 // Check if the swapWith record really IS a version of the original!
699 if (((int) $swapVersion['pid'] == -1 && (int) $curVersion['pid'] >= 0) && !strcmp($swapVersion['t3ver_oid'], $id)) {
700 // Lock file name:
701 $lockFileName = PATH_site . 'typo3temp/swap_locking/' . $table . ':' . $id . '.ser';
702 if (!@is_file($lockFileName)) {
703 // Write lock-file:
704 \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($lockFileName, serialize(array(
705 'tstamp' => $GLOBALS['EXEC_TIME'],
706 'user' => $tcemainObj->BE_USER->user['username'],
707 'curVersion' => $curVersion,
708 'swapVersion' => $swapVersion
709 )));
710 // Find fields to keep
711 $keepFields = $this->getUniqueFields($table);
712 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
713 $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
714 }
715 // l10n-fields must be kept otherwise the localization
716 // will be lost during the publishing
717 if (!isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
718 $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
719 }
720 // Swap "keepfields"
721 foreach ($keepFields as $fN) {
722 $tmp = $swapVersion[$fN];
723 $swapVersion[$fN] = $curVersion[$fN];
724 $curVersion[$fN] = $tmp;
725 }
726 // Preserve states:
727 $t3ver_state = array();
728 $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
729 $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
730 // Modify offline version to become online:
731 $tmp_wsid = $swapVersion['t3ver_wsid'];
732 // Set pid for ONLINE
733 $swapVersion['pid'] = intval($curVersion['pid']);
734 // We clear this because t3ver_oid only make sense for offline versions
735 // and we want to prevent unintentional misuse of this
736 // value for online records.
737 $swapVersion['t3ver_oid'] = 0;
738 // In case of swapping and the offline record has a state
739 // (like 2 or 4 for deleting or move-pointer) we set the
740 // current workspace ID so the record is not deselected
741 // in the interface by \TYPO3\CMS\Backend\Utility\BackendUtility::versioningPlaceholderClause()
742 $swapVersion['t3ver_wsid'] = 0;
743 if ($swapIntoWS) {
744 if ($t3ver_state['swapVersion'] > 0) {
745 $swapVersion['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
746 } else {
747 $swapVersion['t3ver_wsid'] = intval($curVersion['t3ver_wsid']);
748 }
749 }
750 $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
751 $swapVersion['t3ver_stage'] = 0;
752 if (!$swapIntoWS) {
753 $swapVersion['t3ver_state'] = 0;
754 }
755 // Moving element.
756 if ((int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
757 // && $t3ver_state['swapVersion']==4 // Maybe we don't need this?
758 if ($plhRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
759 $movePlhID = $plhRec['uid'];
760 $movePlh['pid'] = $swapVersion['pid'];
761 $swapVersion['pid'] = intval($plhRec['pid']);
762 $curVersion['t3ver_state'] = intval($swapVersion['t3ver_state']);
763 $swapVersion['t3ver_state'] = 0;
764 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
765 // sortby is a "keepFields" which is why this will work...
766 $movePlh[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
767 $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
768 }
769 }
770 }
771 // Take care of relations in each field (e.g. IRRE):
772 if (is_array($GLOBALS['TCA'][$table]['columns'])) {
773 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
774 $this->version_swap_procBasedOnFieldType($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $tcemainObj);
775 }
776 }
777 unset($swapVersion['uid']);
778 // Modify online version to become offline:
779 unset($curVersion['uid']);
780 // Set pid for OFFLINE
781 $curVersion['pid'] = -1;
782 $curVersion['t3ver_oid'] = intval($id);
783 $curVersion['t3ver_wsid'] = $swapIntoWS ? intval($tmp_wsid) : 0;
784 $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
785 $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
786 // Increment lifecycle counter
787 $curVersion['t3ver_stage'] = 0;
788 if (!$swapIntoWS) {
789 $curVersion['t3ver_state'] = 0;
790 }
791 // Registering and swapping MM relations in current and swap records:
792 $tcemainObj->version_remapMMForVersionSwap($table, $id, $swapWith);
793 // Generating proper history data to prepare logging
794 $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
795 $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
796 // Execute swapping:
797 $sqlErrors = array();
798 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $swapVersion);
799 if ($GLOBALS['TYPO3_DB']->sql_error()) {
800 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
801 } else {
802 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($swapWith), $curVersion);
803 if ($GLOBALS['TYPO3_DB']->sql_error()) {
804 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
805 } else {
806 unlink($lockFileName);
807 }
808 }
809 if (!count($sqlErrors)) {
810 // Register swapped ids for later remapping:
811 $this->remappedIds[$table][$id] = $swapWith;
812 $this->remappedIds[$table][$swapWith] = $id;
813 // If a moving operation took place...:
814 if ($movePlhID) {
815 // Remove, if normal publishing:
816 if (!$swapIntoWS) {
817 // For delete + completely delete!
818 $tcemainObj->deleteEl($table, $movePlhID, TRUE, TRUE);
819 } else {
820 // Otherwise update the movePlaceholder:
821 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh);
822 $tcemainObj->addRemapStackRefIndex($table, $movePlhID);
823 }
824 }
825 // Checking for delete:
826 // Delete only if new/deleted placeholders are there.
827 if (!$swapIntoWS && ((int) $t3ver_state['swapVersion'] === 1 || (int) $t3ver_state['swapVersion'] === 2)) {
828 // Force delete
829 $tcemainObj->deleteEl($table, $id, TRUE);
830 }
831 $tcemainObj->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
832 // Update reference index of the live record:
833 $tcemainObj->addRemapStackRefIndex($table, $id);
834 // Set log entry for live record:
835 $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion);
836 if ($propArr['_ORIG_pid'] == -1) {
837 $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
838 } else {
839 $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
840 }
841 $theLogId = $tcemainObj->log($table, $id, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
842 $tcemainObj->setHistory($table, $id, $theLogId);
843 // Update reference index of the offline record:
844 $tcemainObj->addRemapStackRefIndex($table, $swapWith);
845 // Set log entry for offline record:
846 $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion);
847 if ($propArr['_ORIG_pid'] == -1) {
848 $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
849 } else {
850 $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
851 }
852 $theLogId = $tcemainObj->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $swapWith), $propArr['event_pid']);
853 $tcemainObj->setHistory($table, $swapWith, $theLogId);
854
855 $stageId = -20; // Tx_Workspaces_Service_Stages::STAGE_PUBLISH_EXECUTE_ID;
856 if ($notificationEmailInfo) {
857 $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
858 $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = array($wsAccess, $stageId, $comment);
859 $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = $table . ':' . $id;
860 $this->notificationEmailInfo[$notificationEmailInfoKey]['alternativeRecipients'] = $notificationAlternativeRecipients;
861 } else {
862 $this->notifyStageChange($wsAccess, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients);
863 }
864 // Write to log with stageId -20
865 $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
866 $tcemainObj->log($table, $id, 6, 0, 0, 'Published', 30, array('comment' => $comment, 'stage' => $stageId));
867
868 // Clear cache:
869 $tcemainObj->clear_cache($table, $id);
870 // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
871 if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
872 // For delete + completely delete!
873 $tcemainObj->deleteEl($table, $swapWith, TRUE, TRUE);
874 }
875 } else {
876 $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
877 }
878 } else {
879 $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);
880 }
881 } else {
882 $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);
883 }
884 } else {
885 $tcemainObj->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
886 }
887 } else {
888 $tcemainObj->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
889 }
890 } else {
891 $tcemainObj->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
892 }
893 } else {
894 $tcemainObj->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
895 }
896 } else {
897 $tcemainObj->newlog('Error: Either online or swap version could not be selected!', 2);
898 }
899 } else {
900 $tcemainObj->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
901 }
902 }
903
904 /**
905 * Update relations on version/workspace swapping.
906 *
907 * @param string $table: Record Table
908 * @param string $field: Record field
909 * @param array $conf: TCA configuration of current field
910 * @param array $curVersion: Reference to the current (original) record
911 * @param array $swapVersion: Reference to the record (workspace/versionized) to publish in or swap with
912 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
913 * @return void
914 */
915 protected function version_swap_procBasedOnFieldType($table, $field, array $conf, array &$curVersion, array &$swapVersion, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
916 $inlineType = $tcemainObj->getInlineFieldType($conf);
917 // Process pointer fields on normalized database:
918 if ($inlineType == 'field') {
919 // Read relations that point to the current record (e.g. live record):
920 /** @var $dbAnalysisCur \TYPO3\CMS\Core\Database\RelationHandler */
921 $dbAnalysisCur = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
922 $dbAnalysisCur->setUpdateReferenceIndex(FALSE);
923 $dbAnalysisCur->start('', $conf['foreign_table'], '', $curVersion['uid'], $table, $conf);
924 // Read relations that point to the record to be swapped with e.g. draft record):
925 /** @var $dbAnalysisSwap \TYPO3\CMS\Core\Database\RelationHandler */
926 $dbAnalysisSwap = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
927 $dbAnalysisSwap->setUpdateReferenceIndex(FALSE);
928 $dbAnalysisSwap->start('', $conf['foreign_table'], '', $swapVersion['uid'], $table, $conf);
929 // Update relations for both (workspace/versioning) sites:
930 if (count($dbAnalysisCur->itemArray)) {
931 $dbAnalysisCur->writeForeignField($conf, $curVersion['uid'], $swapVersion['uid']);
932 $tcemainObj->addRemapAction($table, $curVersion['uid'], array($this, 'writeRemappedForeignField'), array($dbAnalysisCur, $conf, $swapVersion['uid']));
933 }
934 if (count($dbAnalysisSwap->itemArray)) {
935 $dbAnalysisSwap->writeForeignField($conf, $swapVersion['uid'], $curVersion['uid']);
936 $tcemainObj->addRemapAction($table, $curVersion['uid'], array($this, 'writeRemappedForeignField'), array($dbAnalysisSwap, $conf, $curVersion['uid']));
937 }
938 $items = array_merge($dbAnalysisCur->itemArray, $dbAnalysisSwap->itemArray);
939 foreach ($items as $item) {
940 $tcemainObj->addRemapStackRefIndex($item['table'], $item['id']);
941 }
942 } elseif ($inlineType == 'list') {
943 $tempValue = $curVersion[$field];
944 $curVersion[$field] = $swapVersion[$field];
945 $swapVersion[$field] = $tempValue;
946 }
947 }
948
949 /**
950 * Writes remapped foreign field (IRRE).
951 *
952 * @param \TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis Instance that holds the sorting order of child records
953 * @param array $configuration The TCA field configuration
954 * @param integer $parentId The uid of the parent record
955 * @return void
956 */
957 public function writeRemappedForeignField(\TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis, array $configuration, $parentId) {
958 foreach ($dbAnalysis->itemArray as &$item) {
959 if (isset($this->remappedIds[$item['table']][$item['id']])) {
960 $item['id'] = $this->remappedIds[$item['table']][$item['id']];
961 }
962 }
963 $dbAnalysis->writeForeignField($configuration, $parentId);
964 }
965
966 /**
967 * Release version from this workspace (and into "Live" workspace but as an offline version).
968 *
969 * @param string $table Table name
970 * @param integer $id Record UID
971 * @param boolean $flush If set, will completely delete element
972 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
973 * @return void
974 */
975 protected function version_clearWSID($table, $id, $flush = FALSE, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
976 if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
977 $tcemainObj->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
978 } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
979 if ($liveRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state')) {
980 // Clear workspace ID:
981 $updateData = array(
982 't3ver_wsid' => 0,
983 't3ver_tstamp' => $GLOBALS['EXEC_TIME']
984 );
985 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
986 // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
987 if ((int) $liveRec['t3ver_state'] == 1 || (int) $liveRec['t3ver_state'] == 2) {
988 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($liveRec['uid']), $updateData);
989 // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
990 $tcemainObj->deleteEl($table, $liveRec['uid'], TRUE);
991 }
992 // If "deleted" flag is set for the version that got released
993 // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
994 $wsRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $id);
995 if ($flush || ((int) $wsRec['t3ver_state'] == 1 || (int) $wsRec['t3ver_state'] == 2)) {
996 $tcemainObj->deleteEl($table, $id, TRUE, TRUE);
997 }
998 // Remove the move-placeholder if found for live record.
999 if ((int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
1000 if ($plhRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
1001 $tcemainObj->deleteEl($table, $plhRec['uid'], TRUE, TRUE);
1002 }
1003 }
1004 }
1005 } else {
1006 $tcemainObj->newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
1007 }
1008 }
1009
1010 /*******************************
1011 ***** helper functions ******
1012 *******************************/
1013 /**
1014 * Copies all records from tables in $copyTablesArray from page with $old_pid to page with $new_pid
1015 * Uses raw-copy for the operation (meant for versioning!)
1016 *
1017 * @param integer $oldPageId Current page id.
1018 * @param integer $newPageId New page id
1019 * @param array $copyTablesArray Array of tables from which to copy
1020 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
1021 * @return void
1022 * @see versionizePages()
1023 */
1024 protected function rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
1025 if ($newPageId) {
1026 foreach ($copyTablesArray as $table) {
1027 // all records under the page is copied.
1028 if ($table && is_array($GLOBALS['TCA'][$table]) && $table !== 'pages') {
1029 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid=' . intval($oldPageId) . $tcemainObj->deleteClause($table));
1030 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
1031 // Check, if this record has already been copied by a parent record as relation:
1032 if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) {
1033 // Copying each of the underlying records (method RAW)
1034 $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId);
1035 }
1036 }
1037 $GLOBALS['TYPO3_DB']->sql_free_result($mres);
1038 }
1039 }
1040 }
1041 }
1042
1043 /**
1044 * Finds all elements for swapping versions in workspace
1045 *
1046 * @param string $table Table name of the original element to swap
1047 * @param integer $id UID of the original element to swap (online)
1048 * @param integer $offlineId As above but offline
1049 * @return array Element data. Key is table name, values are array with first element as online UID, second - offline UID
1050 */
1051 public function findPageElementsForVersionSwap($table, $id, $offlineId) {
1052 $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
1053 $workspaceId = $rec['t3ver_wsid'];
1054 $elementData = array();
1055 if ($workspaceId != 0) {
1056 // Get page UID for LIVE and workspace
1057 if ($table != 'pages') {
1058 $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $id, 'pid');
1059 $pageId = $rec['pid'];
1060 $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $pageId);
1061 \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1062 $offlinePageId = $rec['_ORIG_uid'];
1063 } else {
1064 $pageId = $id;
1065 $offlinePageId = $offlineId;
1066 }
1067 // Traversing all tables supporting versioning:
1068 foreach ($GLOBALS['TCA'] as $table => $cfg) {
1069 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1070 $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' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'A') . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'B'));
1071 while (FALSE != ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1072 $elementData[$table][] = array($row[1], $row[0]);
1073 }
1074 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1075 }
1076 }
1077 if ($offlinePageId && $offlinePageId != $pageId) {
1078 $elementData['pages'][] = array($pageId, $offlinePageId);
1079 }
1080 }
1081 return $elementData;
1082 }
1083
1084 /**
1085 * Searches for all elements from all tables on the given pages in the same workspace.
1086 *
1087 * @param array $pageIdList List of PIDs to search
1088 * @param integer $workspaceId Workspace ID
1089 * @param array $elementList List of found elements. Key is table name, value is array of element UIDs
1090 * @return void
1091 */
1092 public function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList) {
1093 if ($workspaceId != 0) {
1094 // Traversing all tables supporting versioning:
1095 foreach ($GLOBALS['TCA'] as $table => $cfg) {
1096 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
1097 $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' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'A') . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'B'));
1098 while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1099 $elementList[$table][] = $row[0];
1100 }
1101 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1102 if (is_array($elementList[$table])) {
1103 // Yes, it is possible to get non-unique array even with DISTINCT above!
1104 // It happens because several UIDs are passed in the array already.
1105 $elementList[$table] = array_unique($elementList[$table]);
1106 }
1107 }
1108 }
1109 }
1110 }
1111
1112 /**
1113 * Finds page UIDs for the element from table <code>$table</code> with UIDs from <code>$idList</code>
1114 *
1115 * @param string $table Table to search
1116 * @param array $idList List of records' UIDs
1117 * @param integer $workspaceId Workspace ID. We need this parameter because user can be in LIVE but he still can publisg DRAFT from ws module!
1118 * @param array $pageIdList List of found page UIDs
1119 * @param array $elementList List of found element UIDs. Key is table name, value is list of UIDs
1120 * @return void
1121 */
1122 public function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList) {
1123 if ($workspaceId != 0) {
1124 $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' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'A') . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'B'));
1125 while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
1126 $pageIdList[] = $row[0];
1127 // Find ws version
1128 // Note: cannot use \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL()
1129 // here because it does not accept workspace id!
1130 $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $row[0]);
1131 \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL('pages', $rec, $workspaceId);
1132 if ($rec['_ORIG_uid']) {
1133 $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
1134 }
1135 }
1136 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1137 // The line below is necessary even with DISTINCT
1138 // because several elements can be passed by caller
1139 $pageIdList = array_unique($pageIdList);
1140 }
1141 }
1142
1143 /**
1144 * Finds real page IDs for state change.
1145 *
1146 * @param array $idList List of page UIDs, possibly versioned
1147 * @return void
1148 */
1149 public function findRealPageIds(array &$idList) {
1150 foreach ($idList as $key => $id) {
1151 $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $id, 't3ver_oid');
1152 if ($rec['t3ver_oid'] > 0) {
1153 $idList[$key] = $rec['t3ver_oid'];
1154 }
1155 }
1156 }
1157
1158 /**
1159 * Creates a move placeholder for workspaces.
1160 * USE ONLY INTERNALLY
1161 * Moving placeholder: Can be done because the system sees it as a placeholder for NEW elements like t3ver_state=1
1162 * Moving original: Will either create the placeholder if it doesn't exist or move existing placeholder in workspace.
1163 *
1164 * @param string $table Table name to move
1165 * @param integer $uid Record uid to move (online record)
1166 * @param integer $destPid Position to move to: $destPid: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
1167 * @param integer $wsUid UID of offline version of online record
1168 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj TCEmain object
1169 * @return void
1170 * @see moveRecord()
1171 */
1172 protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
1173 // If a record gets moved after a record that already has a placeholder record
1174 // then the new placeholder record needs to be after the existing one
1175 $originalRecordDestinationPid = $destPid;
1176 if ($destPid < 0) {
1177 $movePlaceHolder = \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
1178 if ($movePlaceHolder !== FALSE) {
1179 $destPid = -$movePlaceHolder['uid'];
1180 }
1181 }
1182 if ($plh = \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
1183 // If already a placeholder exists, move it:
1184 $tcemainObj->moveRecord_raw($table, $plh['uid'], $destPid);
1185 } else {
1186 // First, we create a placeholder record in the Live workspace that
1187 // represents the position to where the record is eventually moved to.
1188 $newVersion_placeholderFieldArray = array();
1189 if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1190 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1191 }
1192 if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1193 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $tcemainObj->userid;
1194 }
1195 if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1196 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1197 }
1198 if ($table == 'pages') {
1199 // Copy page access settings from original page to placeholder
1200 $perms_clause = $tcemainObj->BE_USER->getPagePermsClause(1);
1201 $access = \TYPO3\CMS\Backend\Utility\BackendUtility::readPageAccess($uid, $perms_clause);
1202 $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
1203 $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
1204 $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
1205 $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
1206 $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
1207 }
1208 $newVersion_placeholderFieldArray['t3ver_label'] = 'MOVE-TO PLACEHOLDER for #' . $uid;
1209 $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
1210 // Setting placeholder state value for temporary record
1211 $newVersion_placeholderFieldArray['t3ver_state'] = 3;
1212 // Setting workspace - only so display of place holders can filter out those from other workspaces.
1213 $newVersion_placeholderFieldArray['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
1214 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = '[MOVE-TO PLACEHOLDER for #' . $uid . ', WS#' . $tcemainObj->BE_USER->workspace . ']';
1215 // moving localized records requires to keep localization-settings for the placeholder too
1216 if (array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl'])) {
1217 $l10nParentRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $uid);
1218 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
1219 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1220 unset($l10nParentRec);
1221 }
1222 // Initially, create at root level.
1223 $newVersion_placeholderFieldArray['pid'] = 0;
1224 $id = 'NEW_MOVE_PLH';
1225 // Saving placeholder as 'original'
1226 $tcemainObj->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE);
1227 // Move the new placeholder from temporary root-level to location:
1228 $tcemainObj->moveRecord_raw($table, $tcemainObj->substNEWwithIDs[$id], $destPid);
1229 // Move the workspace-version of the original to be the version of the move-to-placeholder:
1230 // Setting placeholder state value for version (so it can know it is currently a new version...)
1231 $updateFields = array(
1232 't3ver_state' => 4
1233 );
1234 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsUid), $updateFields);
1235 }
1236 // Check for the localizations of that element and move them as well
1237 $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
1238 }
1239
1240 /**
1241 * Gets all possible child tables that are used on each parent table as field.
1242 *
1243 * @param string $parentTable Name of the parent table
1244 * @param array $possibleInlineChildren Collected possible inline children
1245 * @return array
1246 */
1247 protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) {
1248 foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) {
1249 if (isset($parentFieldDefinition['config']['type'])) {
1250 $parentFieldConfiguration = $parentFieldDefinition['config'];
1251 if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) {
1252 if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) {
1253 $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable($parentFieldConfiguration['foreign_table'], array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table']));
1254 }
1255 }
1256 }
1257 }
1258 return $possibleInlineChildren;
1259 }
1260
1261 /**
1262 * Gets an instance of the command map helper.
1263 *
1264 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain TCEmain object
1265 * @param array $commandMap The command map as submitted to \TYPO3\CMS\Core\DataHandling\DataHandler
1266 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
1267 */
1268 public function getCommandMap(\TYPO3\CMS\Core\DataHandling\DataHandler $tceMain, array $commandMap) {
1269 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Version\\DataHandler\\CommandMap', $this, $tceMain, $commandMap);
1270 }
1271
1272 /**
1273 * Returns all fieldnames from a table which have the unique evaluation type set.
1274 *
1275 * @param string $table Table name
1276 * @return array Array of fieldnames
1277 */
1278 protected function getUniqueFields($table) {
1279 $listArr = array();
1280 if ($GLOBALS['TCA'][$table]['columns']) {
1281 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
1282 if ($configArr['config']['type'] === 'input') {
1283 $evalCodesArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $configArr['config']['eval'], 1);
1284 if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
1285 $listArr[] = $field;
1286 }
1287 }
1288 }
1289 }
1290 return $listArr;
1291 }
1292
1293 }
1294
1295
1296 ?>