|
TYPO3 API
SVNRelease
|
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com) 00006 * (c) 2010-2011 Benjamin Mack (benni@typo3.org) 00007 * 00008 * All rights reserved 00009 * 00010 * This script is part of the TYPO3 project. The TYPO3 project is 00011 * free software; you can redistribute it and/or modify 00012 * it under the terms of the GNU General Public License as published by 00013 * the Free Software Foundation; either version 2 of the License, or 00014 * (at your option) any later version. 00015 * 00016 * The GNU General Public License can be found at 00017 * http://www.gnu.org/copyleft/gpl.html. 00018 * A copy is found in the textfile GPL.txt and important notices to the license 00019 * from the author is found in LICENSE.txt distributed with these scripts. 00020 * 00021 * 00022 * This script is distributed in the hope that it will be useful, 00023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00025 * GNU General Public License for more details. 00026 * 00027 * This copyright notice MUST APPEAR in all copies of the script! 00028 ***************************************************************/ 00029 /** 00030 * 00031 * Contains some parts for staging, versioning and workspaces 00032 * to interact with the TYPO3 Core Engine 00033 * 00034 */ 00035 class tx_version_tcemain { 00036 00037 /** 00038 * For accumulating information about workspace stages raised 00039 * on elements so a single mail is sent as notification. 00040 * previously called "accumulateForNotifEmail" in tcemain 00041 * 00042 * @var array 00043 */ 00044 protected $notificationEmailInfo = array(); 00045 00046 /** 00047 * General comment, eg. for staging in workspaces 00048 * 00049 * @var string 00050 */ 00051 protected $generalComment = ''; 00052 00053 /** 00054 * Contains remapped IDs. 00055 * 00056 * @var array 00057 */ 00058 protected $remappedIds = array(); 00059 00060 /**************************** 00061 ***** Cmdmap Hooks ****** 00062 ****************************/ 00063 00064 /** 00065 * hook that is called before any cmd of the commandmap is executed 00066 * 00067 * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object 00068 * @return void 00069 */ 00070 public function processCmdmap_beforeStart(t3lib_TCEmain $tcemainObj) { 00071 // Reset notification array 00072 $this->notificationEmailInfo = array(); 00073 // Resolve dependencies of version/workspaces actions: 00074 $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj, $tcemainObj->cmdmap)->process()->get(); 00075 } 00076 00077 00078 /** 00079 * hook that is called when no prepared command was found 00080 * 00081 * @param string $command the command to be executed 00082 * @param string $table the table of the record 00083 * @param integer $id the ID of the record 00084 * @param mixed $value the value containing the data 00085 * @param boolean $commandIsProcessed can be set so that other hooks or 00086 * TCEmain knows that the default cmd doesn't have to be called 00087 * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object 00088 * @return void 00089 */ 00090 public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, t3lib_TCEmain $tcemainObj) { 00091 00092 // custom command "version" 00093 if ($command == 'version') { 00094 $commandWasProcessed = TRUE; 00095 $action = (string) $value['action']; 00096 switch ($action) { 00097 00098 case 'new': 00099 // check if page / branch versioning is needed, 00100 // or if "element" version can be used 00101 $versionizeTree = -1; 00102 if (isset($value['treeLevels'])) { 00103 $versionizeTree = t3lib_div::intInRange($value['treeLevels'], -1, 100); 00104 } 00105 if ($table == 'pages' && $versionizeTree >= 0) { 00106 $this->versionizePages($id, $value['label'], $versionizeTree, $tcemainObj); 00107 } else { 00108 $tcemainObj->versionizeRecord($table, $id, $value['label']); 00109 } 00110 break; 00111 00112 case 'swap': 00113 $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'], $tcemainObj); 00114 break; 00115 00116 case 'clearWSID': 00117 $this->version_clearWSID($table, $id, FALSE, $tcemainObj); 00118 break; 00119 00120 case 'flush': 00121 $this->version_clearWSID($table, $id, TRUE, $tcemainObj); 00122 break; 00123 00124 case 'setStage': 00125 $elementIds = t3lib_div::trimExplode(',', $id, TRUE); 00126 foreach ($elementIds as $elementId) { 00127 $this->version_setStage($table, $elementId, $value['stageId'], 00128 (isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment), 00129 TRUE, 00130 $tcemainObj, 00131 $value['notificationAlternativeRecipients'] 00132 ); 00133 } 00134 break; 00135 } 00136 } 00137 } 00138 00139 /** 00140 * hook that is called AFTER all commands of the commandmap was 00141 * executed 00142 * 00143 * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object 00144 * @return void 00145 */ 00146 public function processCmdmap_afterFinish(t3lib_TCEmain $tcemainObj) { 00147 // Empty accumulation array: 00148 foreach ($this->notificationEmailInfo as $notifItem) { 00149 $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $tcemainObj, $notifItem['alternativeRecipients']); 00150 } 00151 00152 // Reset notification array 00153 $this->notificationEmailInfo = array(); 00154 // Reset remapped IDs 00155 $this->remappedIds = array(); 00156 } 00157 00158 00159 /** 00160 * hook that is called AFTER all commands of the commandmap was 00161 * executed 00162 * 00163 * @param string $table the table of the record 00164 * @param integer $id the ID of the record 00165 * @param array $record The accordant database record 00166 * @param boolean $recordWasDeleted can be set so that other hooks or 00167 * TCEmain knows that the default delete action doesn't have to be called 00168 * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object 00169 * @return void 00170 */ 00171 public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, t3lib_TCEmain $tcemainObj) { 00172 // only process the hook if it wasn't processed 00173 // by someone else before 00174 if (!$recordWasDeleted) { 00175 $recordWasDeleted = TRUE; 00176 $id = $record['uid']; 00177 00178 // For Live version, try if there is a workspace version because if so, rather "delete" that instead 00179 // Look, if record is an offline version, then delete directly: 00180 if ($record['pid'] != -1) { 00181 if ($wsVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $id)) { 00182 $record = $wsVersion; 00183 $id = $record['uid']; 00184 } 00185 } 00186 00187 // Look, if record is an offline version, then delete directly: 00188 if ($record['pid'] == -1) { 00189 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) { 00190 // In Live workspace, delete any. In other workspaces there must be match. 00191 if ($tcemainObj->BE_USER->workspace == 0 || (int) $record['t3ver_wsid'] == $tcemainObj->BE_USER->workspace) { 00192 $liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state'); 00193 00194 // Delete those in WS 0 + if their live records state was not "Placeholder". 00195 if ($record['t3ver_wsid']==0 || (int) $liveRec['t3ver_state'] <= 0) { 00196 $tcemainObj->deleteEl($table, $id); 00197 } else { 00198 // If live record was placeholder (new/deleted), rather clear 00199 // it from workspace (because it clears both version and placeholder). 00200 $this->version_clearWSID($table, $id, FALSE, $tcemainObj); 00201 } 00202 } else $tcemainObj->newlog('Tried to delete record from another workspace',1); 00203 } else $tcemainObj->newlog('Versioning not enabled for record with PID = -1!',2); 00204 } elseif ($res = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) { 00205 // Look, if record is "online" or in a versionized branch, then delete directly. 00206 if ($res>0) { 00207 $tcemainObj->deleteEl($table, $id); 00208 } else { 00209 $tcemainObj->newlog('Stage of root point did not allow for deletion',1); 00210 } 00211 } elseif ((int)$record['t3ver_state']===3) { 00212 // Placeholders for moving operations are deletable directly. 00213 00214 // Get record which its a placeholder for and reset the t3ver_state of that: 00215 if ($wsRec = t3lib_BEfunc::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) { 00216 // Clear the state flag of the workspace version of the record 00217 // Setting placeholder state value for version (so it can know it is currently a new version...) 00218 $updateFields = array( 00219 't3ver_state' => 0 00220 ); 00221 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsRec['uid']), $updateFields); 00222 } 00223 $tcemainObj->deleteEl($table, $id); 00224 } else { 00225 // Otherwise, try to delete by versioning: 00226 $tcemainObj->versionizeRecord($table, $id, 'DELETED!', TRUE); 00227 $tcemainObj->deleteL10nOverlayRecords($table, $id); 00228 } 00229 } 00230 } 00231 00232 00233 /** 00234 * hook for t3lib_TCEmain::moveRecord that cares about moving records that 00235 * are *not* in the live workspace 00236 * 00237 * @param string $table the table of the record 00238 * @param integer $id the ID of the record 00239 * @param integer $destPid Position to move to: $destPid: >=0 then it points to 00240 * a page-id on which to insert the record (as the first element). 00241 * <0 then it points to a uid from its own table after which to insert it 00242 * @param array $propArr Record properties, like header and pid (includes workspace overlay) 00243 * @param array $moveRec Record properties, like header and pid (without workspace overlay) 00244 * @param integer $resolvedPid The final page ID of the record 00245 * (workspaces and negative values are resolved) 00246 * @param boolean $recordWasMoved can be set so that other hooks or 00247 * TCEmain knows that the default move action doesn't have to be called 00248 * @param $table the table 00249 */ 00250 public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, t3lib_TCEmain $tcemainObj) { 00251 global $TCA; 00252 00253 // Only do something in Draft workspace 00254 if ($tcemainObj->BE_USER->workspace !== 0) { 00255 $recordWasMoved = TRUE; 00256 00257 // Get workspace version of the source record, if any: 00258 $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid'); 00259 00260 // If no version exists and versioningWS is in version 2, a new placeholder is made automatically: 00261 if (!$WSversion['uid'] && (int)$TCA[$table]['ctrl']['versioningWS']>=2 && (int)$moveRec['t3ver_state']!=3) { 00262 $tcemainObj->versionizeRecord($table, $uid, 'Placeholder version for moving record'); 00263 $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid'); // Will not create new versions in live workspace though... 00264 } 00265 00266 // Check workspace permissions: 00267 $workspaceAccessBlocked = array(); 00268 // Element was in "New/Deleted/Moved" so it can be moved... 00269 $recIsNewVersion = (int)$moveRec['t3ver_state']>0; 00270 00271 $destRes = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table); 00272 $canMoveRecord = $recIsNewVersion || (int)$TCA[$table]['ctrl']['versioningWS'] >= 2; 00273 00274 // Workspace source check: 00275 if (!$recIsNewVersion) { 00276 $errorCode = $tcemainObj->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid); 00277 if ($errorCode) { 00278 $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' '; 00279 } elseif (!$canMoveRecord && $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) { 00280 $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "'.$moveRec['pid'].'" '; 00281 } 00282 } 00283 00284 // Workspace destination check: 00285 00286 // All records can be inserted if $destRes is greater than zero. 00287 // Only new versions can be inserted if $destRes is false. 00288 // NO RECORDS can be inserted if $destRes is negative which indicates a stage 00289 // not allowed for use. If "versioningWS" is version 2, moving can take place of versions. 00290 if (!($destRes > 0 || ($canMoveRecord && !$destRes))) { 00291 $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" '; 00292 } elseif ($destRes == 1 && $WSversion['uid']) { 00293 $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID '; 00294 } 00295 00296 if (!count($workspaceAccessBlocked)) { 00297 // If the move operation is done on a versioned record, which is 00298 // NOT new/deleted placeholder and versioningWS is in version 2, then... 00299 if ($WSversion['uid'] && !$recIsNewVersion && (int)$TCA[$table]['ctrl']['versioningWS'] >= 2) { 00300 $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $tcemainObj); 00301 } else { 00302 // moving not needed, just behave like in live workspace 00303 $recordWasMoved = FALSE; 00304 } 00305 } else { 00306 $tcemainObj->newlog("Move attempt failed due to workspace restrictions: " . implode(' // ', $workspaceAccessBlocked), 1); 00307 } 00308 } 00309 } 00310 00311 00312 00313 /**************************** 00314 ***** Notifications ****** 00315 ****************************/ 00316 00317 /** 00318 * Send an email notification to users in workspace 00319 * 00320 * @param array $stat Workspace access array (from t3lib_userauthgroup::checkWorkspace()) 00321 * @param integer $stageId New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected! 00322 * @param string $table Table name of element (or list of element names if $id is zero) 00323 * @param integer $id Record uid of element (if zero, then $table is used as reference to element(s) alone) 00324 * @param string $comment User comment sent along with action 00325 * @param t3lib_TCEmain $tcemainObj TCEmain object 00326 * @param string $notificationAlternativeRecipients Comma separated list of recipients to notificate instead of be_users selected by sys_workspace, list is generated by workspace extension module 00327 * @return void 00328 */ 00329 protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, t3lib_TCEmain $tcemainObj, $notificationAlternativeRecipients = FALSE) { 00330 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $stat['uid']); 00331 // So, if $id is not set, then $table is taken to be the complete element name! 00332 $elementName = $id ? $table . ':' . $id : $table; 00333 00334 if (is_array($workspaceRec)) { 00335 00336 // Get the new stage title from workspaces library, if workspaces extension is installed 00337 if (t3lib_extMgm::isLoaded('workspaces')) { 00338 $stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages'); 00339 $newStage = $stageService->getStageTitle((int)$stageId); 00340 } else { 00341 // TODO: CONSTANTS SHOULD BE USED - tx_service_workspace_workspaces 00342 // TODO: use localized labels 00343 // Compile label: 00344 switch ((int)$stageId) { 00345 case 1: 00346 $newStage = 'Ready for review'; 00347 break; 00348 case 10: 00349 $newStage = 'Ready for publishing'; 00350 break; 00351 case -1: 00352 $newStage = 'Element was rejected!'; 00353 break; 00354 case 0: 00355 $newStage = 'Rejected element was noticed and edited'; 00356 break; 00357 default: 00358 $newStage = 'Unknown state change!?'; 00359 break; 00360 } 00361 } 00362 00363 if ($notificationAlternativeRecipients == false) { 00364 // Compile list of recipients: 00365 $emails = array(); 00366 switch((int)$stat['stagechg_notification']) { 00367 case 1: 00368 switch((int)$stageId) { 00369 case 1: 00370 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']); 00371 break; 00372 case 10: 00373 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE); 00374 break; 00375 case -1: 00376 # $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']); 00377 # $emails = array_merge($emails,$this->getEmailsForStageChangeNotification($workspaceRec['members'])); 00378 00379 // List of elements to reject: 00380 $allElements = explode(',', $elementName); 00381 // Traverse them, and find the history of each 00382 foreach ($allElements as $elRef) { 00383 list($eTable, $eUid) = explode(':', $elRef); 00384 00385 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows( 00386 'log_data,tstamp,userid', 00387 'sys_log', 00388 'action=6 and details_nr=30 00389 AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($eTable, 'sys_log') . ' 00390 AND recuid=' . intval($eUid), 00391 '', 00392 'uid DESC' 00393 ); 00394 // Find all implicated since the last stage-raise from editing to review: 00395 foreach ($rows as $dat) { 00396 $data = unserialize($dat['log_data']); 00397 00398 $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($dat['userid'], TRUE)); 00399 00400 if ($data['stage'] == 1) { 00401 break; 00402 } 00403 } 00404 } 00405 break; 00406 00407 case 0: 00408 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']); 00409 break; 00410 00411 default: 00412 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE); 00413 break; 00414 } 00415 break; 00416 case 10: 00417 $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE); 00418 $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['reviewers'])); 00419 $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['members'])); 00420 break; 00421 } 00422 } else { 00423 $emails = array(); 00424 foreach ($notificationAlternativeRecipients as $emailAddress) { 00425 $emails[] = array('email' => $emailAddress); 00426 } 00427 } 00428 00429 // prepare and then send the emails 00430 if (count($emails)) { 00431 00432 // Path to record is found: 00433 list($elementTable, $elementUid) = explode(':', $elementName); 00434 $elementUid = intval($elementUid); 00435 $elementRecord = t3lib_BEfunc::getRecord($elementTable, $elementUid); 00436 $recordTitle = t3lib_BEfunc::getRecordTitle($elementTable, $elementRecord); 00437 00438 if ($elementTable == 'pages') { 00439 $pageUid = $elementUid; 00440 } else { 00441 t3lib_BEfunc::fixVersioningPid($elementTable, $elementRecord); 00442 $pageUid = $elementUid = $elementRecord['pid']; 00443 } 00444 00445 // fetch the TSconfig settings for the email 00446 00447 // old way, options are TCEMAIN.notificationEmail_body/subject 00448 $TCEmainTSConfig = $tcemainObj->getTCEMAIN_TSconfig($pageUid); 00449 00450 // these options are deprecated since TYPO3 4.5, but are still 00451 // used in order to provide backwards compatibility 00452 $emailMessage = trim($TCEmainTSConfig['notificationEmail_body']); 00453 $emailSubject = trim($TCEmainTSConfig['notificationEmail_subject']); 00454 00455 // new way, options are 00456 // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject 00457 // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject 00458 $pageTsConfig = t3lib_BEfunc::getPagesTSconfig($pageUid); 00459 $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.']; 00460 00461 $markers = array( 00462 '###RECORD_TITLE###' => $recordTitle, 00463 '###RECORD_PATH###' => t3lib_BEfunc::getRecordPath($elementUid, '', 20), 00464 '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'], 00465 '###SITE_URL###' => t3lib_div::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir, 00466 '###WORKSPACE_TITLE###' => $workspaceRec['title'], 00467 '###WORKSPACE_UID###' => $workspaceRec['uid'], 00468 '###ELEMENT_NAME###' => $elementName, 00469 '###NEXT_STAGE###' => $newStage, 00470 '###COMMENT###' => $comment, 00471 '###USER_REALNAME###' => $tcemainObj->BE_USER->user['realName'], 00472 '###USER_USERNAME###' => $tcemainObj->BE_USER->user['username'] 00473 ); 00474 00475 00476 // sending the emails the old way with sprintf(), 00477 // because it was set explicitly in TSconfig 00478 if ($emailMessage && $emailSubject) { 00479 t3lib_div::deprecationLog('This TYPO3 installation uses Workspaces staging notification by setting the TSconfig options "TCEMAIN.notificationEmail_subject" / "TCEMAIN.notificationEmail_body". Please use the more flexible marker-based options tx_version.workspaces.stageNotificationEmail.message / tx_version.workspaces.stageNotificationEmail.subject'); 00480 00481 $emailSubject = sprintf($emailSubject, $elementName); 00482 $emailMessage = sprintf($emailMessage, 00483 $markers['###SITE_NAME###'], 00484 $markers['###SITE_URL###'], 00485 $markers['###WORKSPACE_TITLE###'], 00486 $markers['###WORKSPACE_UID###'], 00487 $markers['###ELEMENT_NAME###'], 00488 $markers['###NEXT_STAGE###'], 00489 $markers['###COMMENT###'], 00490 $markers['###USER_REALNAME###'], 00491 $markers['###USER_USERNAME###'], 00492 $markers['###RECORD_PATH###'], 00493 $markers['###RECORD_TITLE###'] 00494 ); 00495 00496 // filter out double email addresses 00497 $emailRecipients = array(); 00498 foreach ($emails as $recip) { 00499 $emailRecipients[$recip['email']] = $recip['email']; 00500 } 00501 $emailRecipients = implode(',', $emailRecipients); 00502 00503 // Send one email to everybody 00504 t3lib_div::plainMailEncoded( 00505 $emailRecipients, 00506 $emailSubject, 00507 $emailMessage 00508 ); 00509 } else { 00510 // send an email to each individual user, to ensure the 00511 // multilanguage version of the email 00512 00513 $emailHeaders = $emailConfig['additionalHeaders']; 00514 $emailRecipients = array(); 00515 00516 // an array of language objects that are needed 00517 // for emails with different languages 00518 $languageObjects = array( 00519 $GLOBALS['LANG']->lang => $GLOBALS['LANG'] 00520 ); 00521 00522 // loop through each recipient and send the email 00523 foreach ($emails as $recipientData) { 00524 // don't send an email twice 00525 if (isset($emailRecipients[$recipientData['email']])) { 00526 continue; 00527 } 00528 $emailSubject = $emailConfig['subject']; 00529 $emailMessage = $emailConfig['message']; 00530 $emailRecipients[$recipientData['email']] = $recipientData['email']; 00531 00532 // check if the email needs to be localized 00533 // in the users' language 00534 if (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:') || t3lib_div::isFirstPartOfStr($emailMessage, 'LLL:')) { 00535 $recipientLanguage = ($recipientData['lang'] ? $recipientData['lang'] : 'default'); 00536 if (!isset($languageObjects[$recipientLanguage])) { 00537 // a LANG object in this language hasn't been 00538 // instantiated yet, so this is done here 00539 /** @var $languageObject language */ 00540 $languageObject = t3lib_div::makeInstance('language'); 00541 $languageObject->init($recipientLanguage); 00542 $languageObjects[$recipientLanguage] = $languageObject; 00543 } else { 00544 $languageObject = $languageObjects[$recipientLanguage]; 00545 } 00546 00547 if (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:')) { 00548 $emailSubject = $languageObject->sL($emailSubject); 00549 } 00550 00551 if (t3lib_div::isFirstPartOfStr($emailMessage, 'LLL:')) { 00552 $emailMessage = $languageObject->sL($emailMessage); 00553 } 00554 } 00555 00556 $emailSubject = t3lib_parseHtml::substituteMarkerArray($emailSubject, $markers, '', TRUE, TRUE); 00557 $emailMessage = t3lib_parseHtml::substituteMarkerArray($emailMessage, $markers, '', TRUE, TRUE); 00558 // Send an email to the recipient 00559 t3lib_div::plainMailEncoded( 00560 $recipientData['email'], 00561 $emailSubject, 00562 $emailMessage, 00563 $emailHeaders 00564 ); 00565 } 00566 $emailRecipients = implode(',', $emailRecipients); 00567 } 00568 $tcemainObj->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id); 00569 } 00570 } 00571 } 00572 00573 /** 00574 * Return be_users that should be notified on stage change from input list. 00575 * previously called notifyStageChange_getEmails() in tcemain 00576 * 00577 * @param string $listOfUsers List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set. 00578 * @param boolean $noTablePrefix If set, the input list are integers and not strings. 00579 * @return array Array of emails 00580 */ 00581 protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = FALSE) { 00582 $users = t3lib_div::trimExplode(',', $listOfUsers, 1); 00583 $emails = array(); 00584 foreach ($users as $userIdent) { 00585 if ($noTablePrefix) { 00586 $id = intval($userIdent); 00587 } else { 00588 list($table, $id) = t3lib_div::revExplode('_', $userIdent, 2); 00589 } 00590 if ($table === 'be_users' || $noTablePrefix) { 00591 if ($userRecord = t3lib_BEfunc::getRecord('be_users', $id, 'uid,email,lang,realName')) { 00592 if (strlen(trim($userRecord['email']))) { 00593 $emails[$id] = $userRecord; 00594 } 00595 } 00596 } 00597 } 00598 return $emails; 00599 } 00600 00601 00602 /**************************** 00603 ***** Stage Changes ****** 00604 ****************************/ 00605 00606 /** 00607 * Setting stage of record 00608 * 00609 * @param string $table Table name 00610 * @param integer $integer Record UID 00611 * @param integer $stageId Stage ID to set 00612 * @param string $comment Comment that goes into log 00613 * @param boolean $notificationEmailInfo Accumulate state changes in memory for compiled notification email? 00614 * @param t3lib_TCEmain $tcemainObj TCEmain object 00615 * @param string $notificationAlternativeRecipients comma separated list of recipients to notificate instead of normal be_users 00616 * @return void 00617 */ 00618 protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = FALSE, t3lib_TCEmain $tcemainObj, $notificationAlternativeRecipients = FALSE) { 00619 if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) { 00620 $tcemainObj->newlog('Attempt to set stage for record failed: ' . $errorCode, 1); 00621 } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) { 00622 $record = t3lib_BEfunc::getRecord($table, $id); 00623 $stat = $tcemainObj->BE_USER->checkWorkspace($record['t3ver_wsid']); 00624 // check if the usere is allowed to the current stage, so it's also allowed to send to next stage 00625 if ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($record['t3ver_stage'])) { 00626 00627 // Set stage of record: 00628 $updateData = array( 00629 't3ver_stage' => $stageId 00630 ); 00631 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData); 00632 $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id); 00633 00634 // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere! 00635 $tcemainObj->log($table, $id, 6, 0, 0, 'Stage raised...', 30, array('comment' => $comment, 'stage' => $stageId)); 00636 00637 if ((int)$stat['stagechg_notification'] > 0) { 00638 if ($notificationEmailInfo) { 00639 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = array($stat, $stageId, $comment); 00640 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id; 00641 $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients; 00642 } else { 00643 $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients); 00644 } 00645 } 00646 } else $tcemainObj->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1); 00647 } else $tcemainObj->newlog('Attempt to set stage for record failed because you do not have edit access', 1); 00648 } 00649 00650 00651 00652 /***************************** 00653 ***** CMD versioning ****** 00654 *****************************/ 00655 00656 /** 00657 * Creates a new version of a page including content and possible subpages. 00658 * 00659 * @param integer $uid Page uid to create new version of. 00660 * @param string $label Version label 00661 * @param integer $versionizeTree Indicating "treeLevel" - "page" (0) or "branch" (>=1) ["element" type must call versionizeRecord() directly] 00662 * @param t3lib_TCEmain $tcemainObj TCEmain object 00663 * @return void 00664 * @see copyPages() 00665 */ 00666 protected function versionizePages($uid, $label, $versionizeTree, t3lib_TCEmain $tcemainObj) { 00667 global $TCA; 00668 00669 $uid = intval($uid); 00670 // returns the branch 00671 $brExist = $tcemainObj->doesBranchExist('', $uid, $tcemainObj->pMap['show'], 1); 00672 00673 // Checks if we had permissions 00674 if ($brExist != -1) { 00675 00676 // Make list of tables that should come along with a new version of the page: 00677 $verTablesArray = array(); 00678 $allTables = array_keys($TCA); 00679 foreach ($allTables as $tableName) { 00680 if ($tableName != 'pages' && ($versionizeTree > 0 || $TCA[$tableName]['ctrl']['versioning_followPages'])) { 00681 $verTablesArray[] = $tableName; 00682 } 00683 } 00684 00685 // Remove the possible inline child tables from the tables to be versioniozed automatically: 00686 $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages')); 00687 00688 // Begin to copy pages if we're allowed to: 00689 if ($tcemainObj->BE_USER->workspaceVersioningTypeAccess($versionizeTree)) { 00690 00691 // Versionize this page: 00692 $theNewRootID = $tcemainObj->versionizeRecord('pages', $uid, $label, FALSE, $versionizeTree); 00693 if ($theNewRootID) { 00694 $this->rawCopyPageContent($uid, $theNewRootID, $verTablesArray, $tcemainObj); 00695 00696 // If we're going to copy recursively...: 00697 if ($versionizeTree > 0) { 00698 00699 // Get ALL subpages to copy (read permissions respected - they should NOT be...): 00700 $CPtable = $tcemainObj->int_pageTreeInfo(array(), $uid, intval($versionizeTree), $theNewRootID); 00701 00702 // Now copying the subpages 00703 foreach ($CPtable as $thePageUid => $thePagePid) { 00704 $newPid = $tcemainObj->copyMappingArray['pages'][$thePagePid]; 00705 if (isset($newPid)) { 00706 $theNewRootID = $tcemainObj->copyRecord_raw('pages', $thePageUid, $newPid); 00707 $this->rawCopyPageContent($thePageUid, $theNewRootID, $verTablesArray, $tcemainObj); 00708 } else { 00709 $tcemainObj->newlog('Something went wrong during copying branch (for versioning)', 1); 00710 break; 00711 } 00712 } 00713 } // else the page was not copied. Too bad... 00714 } else $tcemainObj->newlog('The root version could not be created!',1); 00715 } else $tcemainObj->newlog('Versioning type "'.$versionizeTree.'" was not allowed in workspace',1); 00716 } else $tcemainObj->newlog('Could not read all subpages to versionize.',1); 00717 } 00718 00719 00720 /** 00721 * Swapping versions of a record 00722 * 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 00723 * 00724 * @param string $table Table name 00725 * @param integer $id UID of the online record to swap 00726 * @param integer $swapWith UID of the archived version to swap with! 00727 * @param boolean $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace. 00728 * @param t3lib_TCEmain $tcemainObj TCEmain object 00729 * @return void 00730 */ 00731 protected function version_swap($table, $id, $swapWith, $swapIntoWS=0, t3lib_TCEmain $tcemainObj) { 00732 global $TCA; 00733 00734 // First, check if we may actually edit the online record 00735 if ($tcemainObj->checkRecordUpdateAccess($table, $id)) { 00736 00737 // Select the two versions: 00738 $curVersion = t3lib_BEfunc::getRecord($table, $id, '*'); 00739 $swapVersion = t3lib_BEfunc::getRecord($table, $swapWith, '*'); 00740 $movePlh = array(); 00741 $movePlhID = 0; 00742 00743 if (is_array($curVersion) && is_array($swapVersion)) { 00744 if ($tcemainObj->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) { 00745 $wsAccess = $tcemainObj->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']); 00746 if ($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10) { 00747 if ($tcemainObj->doesRecordExist($table,$swapWith,'show') && $tcemainObj->checkRecordUpdateAccess($table,$swapWith)) { 00748 if (!$swapIntoWS || $tcemainObj->BE_USER->workspaceSwapAccess()) { 00749 00750 // Check if the swapWith record really IS a version of the original! 00751 if ((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0 && !strcmp($swapVersion['t3ver_oid'], $id)) { 00752 00753 // Lock file name: 00754 $lockFileName = PATH_site.'typo3temp/swap_locking/' . $table . ':' . $id . '.ser'; 00755 00756 if (!@is_file($lockFileName)) { 00757 00758 // Write lock-file: 00759 t3lib_div::writeFileToTypo3tempDir($lockFileName, serialize(array( 00760 'tstamp' => $GLOBALS['EXEC_TIME'], 00761 'user' => $tcemainObj->BE_USER->user['username'], 00762 'curVersion' => $curVersion, 00763 'swapVersion' => $swapVersion 00764 ))); 00765 00766 // Find fields to keep 00767 $keepFields = $tcemainObj->getUniqueFields($table); 00768 if ($TCA[$table]['ctrl']['sortby']) { 00769 $keepFields[] = $TCA[$table]['ctrl']['sortby']; 00770 } 00771 // l10n-fields must be kept otherwise the localization 00772 // will be lost during the publishing 00773 if (!isset($TCA[$table]['ctrl']['transOrigPointerTable']) && $TCA[$table]['ctrl']['transOrigPointerField']) { 00774 $keepFields[] = $TCA[$table]['ctrl']['transOrigPointerField']; 00775 } 00776 00777 // Swap "keepfields" 00778 foreach ($keepFields as $fN) { 00779 $tmp = $swapVersion[$fN]; 00780 $swapVersion[$fN] = $curVersion[$fN]; 00781 $curVersion[$fN] = $tmp; 00782 } 00783 00784 // Preserve states: 00785 $t3ver_state = array(); 00786 $t3ver_state['swapVersion'] = $swapVersion['t3ver_state']; 00787 $t3ver_state['curVersion'] = $curVersion['t3ver_state']; 00788 00789 // Modify offline version to become online: 00790 $tmp_wsid = $swapVersion['t3ver_wsid']; 00791 // Set pid for ONLINE 00792 $swapVersion['pid'] = intval($curVersion['pid']); 00793 // We clear this because t3ver_oid only make sense for offline versions 00794 // and we want to prevent unintentional misuse of this 00795 // value for online records. 00796 $swapVersion['t3ver_oid'] = 0; 00797 // In case of swapping and the offline record has a state 00798 // (like 2 or 4 for deleting or move-pointer) we set the 00799 // current workspace ID so the record is not deselected 00800 // in the interface by t3lib_BEfunc::versioningPlaceholderClause() 00801 $swapVersion['t3ver_wsid'] = 0; 00802 if ($swapIntoWS) { 00803 if ($t3ver_state['swapVersion'] > 0) { 00804 $swapVersion['t3ver_wsid'] = $tcemainObj->BE_USER->workspace; 00805 } else { 00806 $swapVersion['t3ver_wsid'] = intval($curVersion['t3ver_wsid']); 00807 } 00808 } 00809 $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME']; 00810 $swapVersion['t3ver_stage'] = 0; 00811 if (!$swapIntoWS) { 00812 $swapVersion['t3ver_state'] = 0; 00813 } 00814 00815 // Moving element. 00816 if ((int)$TCA[$table]['ctrl']['versioningWS']>=2) { // && $t3ver_state['swapVersion']==4 // Maybe we don't need this? 00817 if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($TCA[$table]['ctrl']['sortby'] ? ',' . $TCA[$table]['ctrl']['sortby'] : ''))) { 00818 $movePlhID = $plhRec['uid']; 00819 $movePlh['pid'] = $swapVersion['pid']; 00820 $swapVersion['pid'] = intval($plhRec['pid']); 00821 00822 $curVersion['t3ver_state'] = intval($swapVersion['t3ver_state']); 00823 $swapVersion['t3ver_state'] = 0; 00824 00825 if ($TCA[$table]['ctrl']['sortby']) { 00826 // sortby is a "keepFields" which is why this will work... 00827 $movePlh[$TCA[$table]['ctrl']['sortby']] = $swapVersion[$TCA[$table]['ctrl']['sortby']]; 00828 $swapVersion[$TCA[$table]['ctrl']['sortby']] = $plhRec[$TCA[$table]['ctrl']['sortby']]; 00829 } 00830 } 00831 } 00832 00833 // Take care of relations in each field (e.g. IRRE): 00834 if (is_array($GLOBALS['TCA'][$table]['columns'])) { 00835 foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) { 00836 $this->version_swap_procBasedOnFieldType( 00837 $table, $field, $fieldConf['config'], $curVersion, $swapVersion, $tcemainObj 00838 ); 00839 } 00840 } 00841 unset($swapVersion['uid']); 00842 00843 // Modify online version to become offline: 00844 unset($curVersion['uid']); 00845 // Set pid for OFFLINE 00846 $curVersion['pid'] = -1; 00847 $curVersion['t3ver_oid'] = intval($id); 00848 $curVersion['t3ver_wsid'] = ($swapIntoWS ? intval($tmp_wsid) : 0); 00849 $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME']; 00850 $curVersion['t3ver_count'] = $curVersion['t3ver_count']+1; // Increment lifecycle counter 00851 $curVersion['t3ver_stage'] = 0; 00852 if (!$swapIntoWS) { 00853 $curVersion['t3ver_state'] = 0; 00854 } 00855 00856 // Keeping the swapmode state 00857 if ($table === 'pages') { 00858 $curVersion['t3ver_swapmode'] = $swapVersion['t3ver_swapmode']; 00859 } 00860 00861 // Registering and swapping MM relations in current and swap records: 00862 $tcemainObj->version_remapMMForVersionSwap($table, $id, $swapWith); 00863 00864 // Generating proper history data to prepare logging 00865 $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion); 00866 $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion); 00867 00868 // Execute swapping: 00869 $sqlErrors = array(); 00870 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $swapVersion); 00871 if ($GLOBALS['TYPO3_DB']->sql_error()) { 00872 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error(); 00873 } else { 00874 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($swapWith), $curVersion); 00875 if ($GLOBALS['TYPO3_DB']->sql_error()) { 00876 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error(); 00877 } else { 00878 unlink($lockFileName); 00879 } 00880 } 00881 00882 if (!count($sqlErrors)) { 00883 // Register swapped ids for later remapping: 00884 $this->remappedIds[$table][$id] =$swapWith; 00885 $this->remappedIds[$table][$swapWith] = $id; 00886 00887 // If a moving operation took place...: 00888 if ($movePlhID) { 00889 // Remove, if normal publishing: 00890 if (!$swapIntoWS) { 00891 // For delete + completely delete! 00892 $tcemainObj->deleteEl($table, $movePlhID, TRUE, TRUE); 00893 } else { 00894 // Otherwise update the movePlaceholder: 00895 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh); 00896 $tcemainObj->addRemapStackRefIndex($table, $movePlhID); 00897 } 00898 } 00899 00900 // Checking for delete: 00901 // Delete only if new/deleted placeholders are there. 00902 if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) { 00903 // Force delete 00904 $tcemainObj->deleteEl($table, $id, TRUE); 00905 } 00906 00907 $tcemainObj->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']); 00908 00909 // Update reference index of the live record: 00910 $tcemainObj->addRemapStackRefIndex($table, $id); 00911 00912 // Set log entry for live record: 00913 $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion); 00914 if ($propArr['_ORIG_pid'] == -1) { 00915 $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated'); 00916 } else { 00917 $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated'); 00918 } 00919 $theLogId = $tcemainObj->log($table, $id, 2, $propArr['pid'], 0, $label , 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']); 00920 $tcemainObj->setHistory($table, $id, $theLogId); 00921 00922 // Update reference index of the offline record: 00923 $tcemainObj->addRemapStackRefIndex($table, $swapWith); 00924 // Set log entry for offline record: 00925 $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion); 00926 if ($propArr['_ORIG_pid'] == -1) { 00927 $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated'); 00928 } else { 00929 $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated'); 00930 } 00931 $theLogId = $tcemainObj->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $swapWith), $propArr['event_pid']); 00932 $tcemainObj->setHistory($table, $swapWith, $theLogId); 00933 00934 // SWAPPING pids for subrecords: 00935 if ($table=='pages' && $swapVersion['t3ver_swapmode'] >= 0) { 00936 00937 // Collect table names that should be copied along with the tables: 00938 foreach ($TCA as $tN => $tCfg) { 00939 // For "Branch" publishing swap ALL, 00940 // otherwise for "page" publishing, swap only "versioning_followPages" tables 00941 if ($swapVersion['t3ver_swapmode'] > 0 || $TCA[$tN]['ctrl']['versioning_followPages']) { 00942 $temporaryPid = -($id+1000000); 00943 00944 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($id), array('pid' => $temporaryPid)); 00945 if ($GLOBALS['TYPO3_DB']->sql_error()) { 00946 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error(); 00947 } 00948 00949 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($swapWith), array('pid' => $id)); 00950 if ($GLOBALS['TYPO3_DB']->sql_error()) { 00951 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error(); 00952 } 00953 00954 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($temporaryPid), array('pid' => $swapWith)); 00955 if ($GLOBALS['TYPO3_DB']->sql_error()) { 00956 $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error(); 00957 } 00958 00959 if (count($sqlErrors)) { 00960 $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2); 00961 } 00962 } 00963 } 00964 } 00965 // Clear cache: 00966 $tcemainObj->clear_cache($table, $id); 00967 00968 // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!): 00969 if (!$swapIntoWS && $t3ver_state['curVersion']>0) { 00970 // For delete + completely delete! 00971 $tcemainObj->deleteEl($table, $swapWith, TRUE, TRUE); 00972 } 00973 } else $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2); 00974 } 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); 00975 } 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); 00976 } else $tcemainObj->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1); 00977 } else $tcemainObj->newlog('You cannot publish a record you do not have edit and show permissions for', 1); 00978 } else $tcemainObj->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1); 00979 } else $tcemainObj->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1); 00980 } else $tcemainObj->newlog('Error: Either online or swap version could not be selected!', 2); 00981 } else $tcemainObj->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1); 00982 } 00983 00984 00985 /** 00986 * Update relations on version/workspace swapping. 00987 * 00988 * @param string $table: Record Table 00989 * @param string $field: Record field 00990 * @param array $conf: TCA configuration of current field 00991 * @param array $curVersion: Reference to the current (original) record 00992 * @param array $swapVersion: Reference to the record (workspace/versionized) to publish in or swap with 00993 * @param t3lib_TCEmain $tcemainObj TCEmain object 00994 * @return void 00995 */ 00996 protected function version_swap_procBasedOnFieldType($table, $field, array $conf, array &$curVersion, array &$swapVersion, t3lib_TCEmain $tcemainObj) { 00997 $inlineType = $tcemainObj->getInlineFieldType($conf); 00998 00999 // Process pointer fields on normalized database: 01000 if ($inlineType == 'field') { 01001 // Read relations that point to the current record (e.g. live record): 01002 /** @var $dbAnalysisCur t3lib_loadDBGroup */ 01003 $dbAnalysisCur = t3lib_div::makeInstance('t3lib_loadDBGroup'); 01004 $dbAnalysisCur->setUpdateReferenceIndex(FALSE); 01005 $dbAnalysisCur->start('', $conf['foreign_table'], '', $curVersion['uid'], $table, $conf); 01006 // Read relations that point to the record to be swapped with e.g. draft record): 01007 /** @var $dbAnalysisSwap t3lib_loadDBGroup */ 01008 $dbAnalysisSwap = t3lib_div::makeInstance('t3lib_loadDBGroup'); 01009 $dbAnalysisSwap->setUpdateReferenceIndex(FALSE); 01010 $dbAnalysisSwap->start('', $conf['foreign_table'], '', $swapVersion['uid'], $table, $conf); 01011 // Update relations for both (workspace/versioning) sites: 01012 01013 if (count($dbAnalysisCur->itemArray)) { 01014 $dbAnalysisCur->writeForeignField($conf, $curVersion['uid'], $swapVersion['uid']); 01015 $tcemainObj->addRemapAction( 01016 $table, $curVersion['uid'], 01017 array($this, 'writeRemappedForeignField'), 01018 array($dbAnalysisCur, $conf, $swapVersion['uid']) 01019 ); 01020 } 01021 01022 if (count($dbAnalysisSwap->itemArray)) { 01023 $dbAnalysisSwap->writeForeignField($conf, $swapVersion['uid'], $curVersion['uid']); 01024 $tcemainObj->addRemapAction( 01025 $table, $curVersion['uid'], 01026 array($this, 'writeRemappedForeignField'), 01027 array($dbAnalysisSwap, $conf, $curVersion['uid']) 01028 ); 01029 } 01030 01031 $items = array_merge($dbAnalysisCur->itemArray, $dbAnalysisSwap->itemArray); 01032 foreach ($items as $item) { 01033 $tcemainObj->addRemapStackRefIndex($item['table'], $item['id']); 01034 } 01035 01036 // Swap field values (CSV): 01037 // BUT: These values will be swapped back in the next steps, when the *CHILD RECORD ITSELF* is swapped! 01038 } elseif ($inlineType == 'list') { 01039 $tempValue = $curVersion[$field]; 01040 $curVersion[$field] = $swapVersion[$field]; 01041 $swapVersion[$field] = $tempValue; 01042 } 01043 } 01044 01045 /** 01046 * Writes remapped foreign field (IRRE). 01047 * 01048 * @param t3lib_loadDBGroup $dbAnalysis Instance that holds the sorting order of child records 01049 * @param array $configuration The TCA field configuration 01050 * @param integer $parentId The uid of the parent record 01051 * @return void 01052 */ 01053 public function writeRemappedForeignField(t3lib_loadDBGroup $dbAnalysis, array $configuration, $parentId) { 01054 foreach ($dbAnalysis->itemArray as &$item) { 01055 if (isset($this->remappedIds[$item['table']][$item['id']])) { 01056 $item['id'] = $this->remappedIds[$item['table']][$item['id']]; 01057 } 01058 } 01059 01060 $dbAnalysis->writeForeignField($configuration, $parentId); 01061 } 01062 01063 01064 01065 /** 01066 * Release version from this workspace (and into "Live" workspace but as an offline version). 01067 * 01068 * @param string $table Table name 01069 * @param integer $id Record UID 01070 * @param boolean $flush If set, will completely delete element 01071 * @param t3lib_TCEmain $tcemainObj TCEmain object 01072 * @return void 01073 */ 01074 protected function version_clearWSID($table, $id, $flush = FALSE, t3lib_TCEmain $tcemainObj) { 01075 global $TCA; 01076 01077 if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) { 01078 $tcemainObj->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1); 01079 } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) { 01080 if ($liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state')) { 01081 // Clear workspace ID: 01082 $updateData = array( 01083 't3ver_wsid' => 0 01084 ); 01085 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData); 01086 01087 // Clear workspace ID for live version AND DELETE IT as well because it is a new record! 01088 if ((int) $liveRec['t3ver_state'] == 1 || (int) $liveRec['t3ver_state'] == 2) { 01089 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid=' . intval($liveRec['uid']), $updateData); 01090 // THIS assumes that the record was placeholder ONLY for ONE record (namely $id) 01091 $tcemainObj->deleteEl($table, $liveRec['uid'], TRUE); 01092 } 01093 01094 // If "deleted" flag is set for the version that got released 01095 // it doesn't make sense to keep that "placeholder" anymore and we delete it completly. 01096 $wsRec = t3lib_BEfunc::getRecord($table, $id); 01097 if ($flush || ((int) $wsRec['t3ver_state'] == 1 || (int) $wsRec['t3ver_state'] == 2)) { 01098 $tcemainObj->deleteEl($table, $id, TRUE, TRUE); 01099 } 01100 01101 // Remove the move-placeholder if found for live record. 01102 if ((int)$TCA[$table]['ctrl']['versioningWS'] >= 2) { 01103 if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $liveRec['uid'], 'uid')) { 01104 $tcemainObj->deleteEl($table, $plhRec['uid'], TRUE, TRUE); 01105 } 01106 } 01107 } 01108 } else $tcemainObj->newlog('Attempt to reset workspace for record failed because you do not have edit access',1); 01109 } 01110 01111 01112 /******************************* 01113 ***** helper functions ****** 01114 *******************************/ 01115 01116 01117 /** 01118 * Copies all records from tables in $copyTablesArray from page with $old_pid to page with $new_pid 01119 * Uses raw-copy for the operation (meant for versioning!) 01120 * 01121 * @param integer $oldPageId Current page id. 01122 * @param integer $newPageId New page id 01123 * @param array $copyTablesArray Array of tables from which to copy 01124 * @param t3lib_TCEmain $tcemainObj TCEmain object 01125 * @return void 01126 * @see versionizePages() 01127 */ 01128 protected function rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, t3lib_TCEmain $tcemainObj) { 01129 global $TCA; 01130 01131 if ($newPageId) { 01132 foreach ($copyTablesArray as $table) { 01133 // all records under the page is copied. 01134 if ($table && is_array($TCA[$table]) && $table != 'pages') { 01135 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery( 01136 'uid', 01137 $table, 01138 'pid=' . intval($oldPageId) . $tcemainObj->deleteClause($table) 01139 ); 01140 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) { 01141 // Check, if this record has already been copied by a parent record as relation: 01142 if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) { 01143 // Copying each of the underlying records (method RAW) 01144 $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId); 01145 } 01146 } 01147 $GLOBALS['TYPO3_DB']->sql_free_result($mres); 01148 } 01149 } 01150 } 01151 } 01152 01153 01154 /** 01155 * Finds all elements for swapping versions in workspace 01156 * 01157 * @param string $table Table name of the original element to swap 01158 * @param integer $id UID of the original element to swap (online) 01159 * @param integer $offlineId As above but offline 01160 * @return array Element data. Key is table name, values are array with first element as online UID, second - offline UID 01161 */ 01162 protected function findPageElementsForVersionSwap($table, $id, $offlineId) { 01163 global $TCA; 01164 01165 $rec = t3lib_BEfunc::getRecord($table, $offlineId, 't3ver_wsid'); 01166 $workspaceId = $rec['t3ver_wsid']; 01167 01168 $elementData = array(); 01169 if ($workspaceId != 0) { 01170 // Get page UID for LIVE and workspace 01171 if ($table != 'pages') { 01172 $rec = t3lib_BEfunc::getRecord($table, $id, 'pid'); 01173 $pageId = $rec['pid']; 01174 $rec = t3lib_BEfunc::getRecord('pages', $pageId); 01175 t3lib_BEfunc::workspaceOL('pages', $rec, $workspaceId); 01176 $offlinePageId = $rec['_ORIG_uid']; 01177 } else { 01178 $pageId = $id; 01179 $offlinePageId = $offlineId; 01180 } 01181 01182 // Traversing all tables supporting versioning: 01183 foreach ($TCA as $table => $cfg) { 01184 if ($TCA[$table]['ctrl']['versioningWS'] && $table != 'pages') { 01185 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('A.uid AS offlineUid, B.uid AS uid', 01186 $table . ' A,' . $table . ' B', 01187 'A.pid=-1 AND B.pid=' . $pageId . ' AND A.t3ver_wsid=' . $workspaceId . 01188 ' AND B.uid=A.t3ver_oid' . 01189 t3lib_BEfunc::deleteClause($table, 'A') . t3lib_BEfunc::deleteClause($table, 'B')); 01190 while (FALSE != ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) { 01191 $elementData[$table][] = array($row[1], $row[0]); 01192 } 01193 $GLOBALS['TYPO3_DB']->sql_free_result($res); 01194 } 01195 } 01196 if ($offlinePageId && $offlinePageId != $pageId) { 01197 $elementData['pages'][] = array($pageId, $offlinePageId); 01198 } 01199 } 01200 return $elementData; 01201 } 01202 01203 /** 01204 * Searches for all elements from all tables on the given pages in the same workspace. 01205 * 01206 * @param array $pageIdList List of PIDs to search 01207 * @param integer $workspaceId Workspace ID 01208 * @param array $elementList List of found elements. Key is table name, value is array of element UIDs 01209 * @return void 01210 */ 01211 protected function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList) { 01212 global $TCA; 01213 01214 if ($workspaceId != 0) { 01215 // Traversing all tables supporting versioning: 01216 foreach ($TCA as $table => $cfg) { 01217 if ($TCA[$table]['ctrl']['versioningWS'] && $table != 'pages') { 01218 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT A.uid', 01219 $table . ' A,' . $table . ' B', 01220 'A.pid=-1' . // Offline version 01221 ' AND A.t3ver_wsid=' . $workspaceId . 01222 ' AND B.pid IN (' . implode(',', $pageIdList) . ') AND A.t3ver_oid=B.uid' . 01223 t3lib_BEfunc::deleteClause($table,'A'). 01224 t3lib_BEfunc::deleteClause($table,'B') 01225 ); 01226 while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) { 01227 $elementList[$table][] = $row[0]; 01228 } 01229 $GLOBALS['TYPO3_DB']->sql_free_result($res); 01230 if (is_array($elementList[$table])) { 01231 // Yes, it is possible to get non-unique array even with DISTINCT above! 01232 // It happens because several UIDs are passed in the array already. 01233 $elementList[$table] = array_unique($elementList[$table]); 01234 } 01235 } 01236 } 01237 } 01238 } 01239 01240 01241 /** 01242 * Finds page UIDs for the element from table <code>$table</code> with UIDs from <code>$idList</code> 01243 * 01244 * @param string $table Table to search 01245 * @param array $idList List of records' UIDs 01246 * @param integer $workspaceId Workspace ID. We need this parameter because user can be in LIVE but he still can publisg DRAFT from ws module! 01247 * @param array $pageIdList List of found page UIDs 01248 * @param array $elementList List of found element UIDs. Key is table name, value is list of UIDs 01249 * @return void 01250 */ 01251 protected function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList) { 01252 if ($workspaceId != 0) { 01253 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT B.pid', 01254 $table . ' A,' . $table . ' B', 01255 'A.pid=-1' . // Offline version 01256 ' AND A.t3ver_wsid=' . $workspaceId . 01257 ' AND A.uid IN (' . implode(',', $idList) . ') AND A.t3ver_oid=B.uid' . 01258 t3lib_BEfunc::deleteClause($table,'A'). 01259 t3lib_BEfunc::deleteClause($table,'B') 01260 ); 01261 while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) { 01262 $pageIdList[] = $row[0]; 01263 // Find ws version 01264 // Note: cannot use t3lib_BEfunc::getRecordWSOL() 01265 // here because it does not accept workspace id! 01266 $rec = t3lib_BEfunc::getRecord('pages', $row[0]); 01267 t3lib_BEfunc::workspaceOL('pages', $rec, $workspaceId); 01268 if ($rec['_ORIG_uid']) { 01269 $elementList['pages'][$row[0]] = $rec['_ORIG_uid']; 01270 } 01271 } 01272 $GLOBALS['TYPO3_DB']->sql_free_result($res); 01273 // The line below is necessary even with DISTINCT 01274 // because several elements can be passed by caller 01275 $pageIdList = array_unique($pageIdList); 01276 } 01277 } 01278 01279 01280 /** 01281 * Finds real page IDs for state change. 01282 * 01283 * @param array $idList List of page UIDs, possibly versioned 01284 * @return void 01285 */ 01286 protected function findRealPageIds(array &$idList) { 01287 foreach ($idList as $key => $id) { 01288 $rec = t3lib_BEfunc::getRecord('pages', $id, 't3ver_oid'); 01289 if ($rec['t3ver_oid'] > 0) { 01290 $idList[$key] = $rec['t3ver_oid']; 01291 } 01292 } 01293 } 01294 01295 01296 /** 01297 * Creates a move placeholder for workspaces. 01298 * USE ONLY INTERNALLY 01299 * Moving placeholder: Can be done because the system sees it as a placeholder for NEW elements like t3ver_state=1 01300 * Moving original: Will either create the placeholder if it doesn't exist or move existing placeholder in workspace. 01301 * 01302 * @param string $table Table name to move 01303 * @param integer $uid Record uid to move (online record) 01304 * @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 01305 * @param integer $wsUid UID of offline version of online record 01306 * @param t3lib_TCEmain $tcemainObj TCEmain object 01307 * @return void 01308 * @see moveRecord() 01309 */ 01310 protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, t3lib_TCEmain $tcemainObj) { 01311 global $TCA; 01312 01313 if ($plh = t3lib_BEfunc::getMovePlaceholder($table, $uid, 'uid')) { 01314 // If already a placeholder exists, move it: 01315 $tcemainObj->moveRecord_raw($table, $plh['uid'], $destPid); 01316 } else { 01317 // First, we create a placeholder record in the Live workspace that 01318 // represents the position to where the record is eventually moved to. 01319 $newVersion_placeholderFieldArray = array(); 01320 if ($TCA[$table]['ctrl']['crdate']) { 01321 $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME']; 01322 } 01323 if ($TCA[$table]['ctrl']['cruser_id']) { 01324 $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['cruser_id']] = $tcemainObj->userid; 01325 } 01326 if ($TCA[$table]['ctrl']['tstamp']) { 01327 $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME']; 01328 } 01329 01330 if ($table == 'pages') { 01331 // Copy page access settings from original page to placeholder 01332 $perms_clause = $tcemainObj->BE_USER->getPagePermsClause(1); 01333 $access = t3lib_BEfunc::readPageAccess($uid, $perms_clause); 01334 01335 $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid']; 01336 $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid']; 01337 $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user']; 01338 $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group']; 01339 $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody']; 01340 } 01341 01342 $newVersion_placeholderFieldArray['t3ver_label'] = 'MOVE-TO PLACEHOLDER for #' . $uid; 01343 $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid; 01344 // Setting placeholder state value for temporary record 01345 $newVersion_placeholderFieldArray['t3ver_state'] = 3; 01346 01347 // Setting workspace - only so display of place holders can filter out those from other workspaces. 01348 $newVersion_placeholderFieldArray['t3ver_wsid'] = $tcemainObj->BE_USER->workspace; 01349 $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['label']] = '[MOVE-TO PLACEHOLDER for #' . $uid . ', WS#' . $tcemainObj->BE_USER->workspace . ']'; 01350 01351 // moving localized records requires to keep localization-settings for the placeholder too 01352 if (array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl'])) { 01353 $l10nParentRec = t3lib_BEfunc::getRecord($table, $uid); 01354 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']]; 01355 $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]; 01356 unset($l10nParentRec); 01357 } 01358 01359 // Initially, create at root level. 01360 $newVersion_placeholderFieldArray['pid'] = 0; 01361 $id = 'NEW_MOVE_PLH'; 01362 // Saving placeholder as 'original' 01363 $tcemainObj->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE); 01364 01365 // Move the new placeholder from temporary root-level to location: 01366 $tcemainObj->moveRecord_raw($table, $tcemainObj->substNEWwithIDs[$id], $destPid); 01367 01368 // Move the workspace-version of the original to be the version of the move-to-placeholder: 01369 // Setting placeholder state value for version (so it can know it is currently a new version...) 01370 $updateFields = array( 01371 't3ver_state' => 4 01372 ); 01373 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsUid), $updateFields); 01374 } 01375 01376 // Check for the localizations of that element and move them as well 01377 $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid); 01378 } 01379 01380 /** 01381 * Gets all possible child tables that are used on each parent table as field. 01382 * 01383 * @param string $parentTable Name of the parent table 01384 * @param array $possibleInlineChildren Collected possible inline children 01385 * (will be filled automatically during recursive calls) 01386 * @return array 01387 */ 01388 protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) { 01389 t3lib_div::loadTCA($parentTable); 01390 01391 foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) { 01392 if (isset($parentFieldDefinition['config']['type'])) { 01393 $parentFieldConfiguration = $parentFieldDefinition['config']; 01394 if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) { 01395 if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) { 01396 $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable( 01397 $parentFieldConfiguration['foreign_table'], 01398 array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table']) 01399 ); 01400 } 01401 } 01402 } 01403 } 01404 01405 return $possibleInlineChildren; 01406 } 01407 01408 /** 01409 * Gets an instance of the command map helper. 01410 * 01411 * @param t3lib_TCEmain $tceMain TCEmain object 01412 * @param array $commandMap The command map as submitted to t3lib_TCEmain 01413 * @return tx_version_tcemain_CommandMap 01414 */ 01415 public function getCommandMap(t3lib_TCEmain $tceMain, array $commandMap) { 01416 return t3lib_div::makeInstance('tx_version_tcemain_CommandMap', $this, $tceMain, $commandMap); 01417 } 01418 } 01419 01420 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain.php'])) { 01421 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain.php']); 01422 } 01423 ?>
1.8.0