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