/**
  * Swapping versions of a record
  * 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
  *
  * @param	string		Table name
  * @param	integer		UID of the online record to swap
  * @param	integer		UID of the archived version to swap with!
  * @param	boolean		If set, swaps online into workspace instead of publishing out of workspace.
  * @return	void
  */
 function version_swap($table, $id, $swapWith, $swapIntoWS = 0)
 {
     global $TCA;
     // First, check if we may actually edit the online record
     if ($this->checkRecordUpdateAccess($table, $id)) {
         // Select the two versions:
         $curVersion = t3lib_BEfunc::getRecord($table, $id, '*');
         $swapVersion = t3lib_BEfunc::getRecord($table, $swapWith, '*');
         $movePlh = array();
         $movePlhID = 0;
         if (is_array($curVersion) && is_array($swapVersion)) {
             if ($this->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
                 $wsAccess = $this->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
                 if ($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int) $swapVersion['t3ver_stage'] === 10) {
                     if ($this->doesRecordExist($table, $swapWith, 'show') && $this->checkRecordUpdateAccess($table, $swapWith)) {
                         if (!$swapIntoWS || $this->BE_USER->workspaceSwapAccess()) {
                             // Check if the swapWith record really IS a version of the original!
                             if ((int) $swapVersion['pid'] == -1 && (int) $curVersion['pid'] >= 0 && !strcmp($swapVersion['t3ver_oid'], $id)) {
                                 // Lock file name:
                                 $lockFileName = PATH_site . 'typo3temp/swap_locking/' . $table . ':' . $id . '.ser';
                                 if (!@is_file($lockFileName)) {
                                     // Write lock-file:
                                     t3lib_div::writeFileToTypo3tempDir($lockFileName, serialize(array('tstamp' => $GLOBALS['EXEC_TIME'], 'user' => $this->BE_USER->user['username'], 'curVersion' => $curVersion, 'swapVersion' => $swapVersion)));
                                     // Find fields to keep
                                     $keepFields = $this->getUniqueFields($table);
                                     if ($TCA[$table]['ctrl']['sortby']) {
                                         $keepFields[] = $TCA[$table]['ctrl']['sortby'];
                                     }
                                     // l10n-fields must be kept otherwise the localization will be lost during the publishing
                                     if (!isset($TCA[$table]['ctrl']['transOrigPointerTable']) && $TCA[$table]['ctrl']['transOrigPointerField']) {
                                         $keepFields[] = $TCA[$table]['ctrl']['transOrigPointerField'];
                                     }
                                     // Swap "keepfields"
                                     foreach ($keepFields as $fN) {
                                         $tmp = $swapVersion[$fN];
                                         $swapVersion[$fN] = $curVersion[$fN];
                                         $curVersion[$fN] = $tmp;
                                     }
                                     // Preserve states:
                                     $t3ver_state = array();
                                     $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
                                     $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
                                     // Modify offline version to become online:
                                     $tmp_wsid = $swapVersion['t3ver_wsid'];
                                     $swapVersion['pid'] = intval($curVersion['pid']);
                                     // Set pid for ONLINE
                                     $swapVersion['t3ver_oid'] = 0;
                                     // We clear this because t3ver_oid only make sense for offline versions and we want to prevent unintentional misuse of this value for online records.
                                     $swapVersion['t3ver_wsid'] = $swapIntoWS ? $t3ver_state['swapVersion'] > 0 ? $this->BE_USER->workspace : intval($curVersion['t3ver_wsid']) : 0;
                                     // In case of swapping and the offline record has a state (like 2 or 4 for deleting or move-pointer) we set the current workspace ID so the record is not deselected in the interface by t3lib_BEfunc::versioningPlaceholderClause()
                                     $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
                                     $swapVersion['t3ver_stage'] = 0;
                                     if (!$swapIntoWS) {
                                         $swapVersion['t3ver_state'] = 0;
                                     }
                                     // Moving element.
                                     if ((int) $TCA[$table]['ctrl']['versioningWS'] >= 2) {
                                         //  && $t3ver_state['swapVersion']==4   // Maybe we don't need this?
                                         if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($TCA[$table]['ctrl']['sortby'] ? ',' . $TCA[$table]['ctrl']['sortby'] : ''))) {
                                             $movePlhID = $plhRec['uid'];
                                             $movePlh['pid'] = $swapVersion['pid'];
                                             $swapVersion['pid'] = intval($plhRec['pid']);
                                             $curVersion['t3ver_state'] = intval($swapVersion['t3ver_state']);
                                             $swapVersion['t3ver_state'] = 0;
                                             if ($TCA[$table]['ctrl']['sortby']) {
                                                 $movePlh[$TCA[$table]['ctrl']['sortby']] = $swapVersion[$TCA[$table]['ctrl']['sortby']];
                                                 // sortby is a "keepFields" which is why this will work...
                                                 $swapVersion[$TCA[$table]['ctrl']['sortby']] = $plhRec[$TCA[$table]['ctrl']['sortby']];
                                             }
                                         }
                                     }
                                     // Take care of relations in each field (e.g. IRRE):
                                     if (is_array($GLOBALS['TCA'][$table]['columns'])) {
                                         foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
                                             $this->version_swap_procBasedOnFieldType($table, $field, $fieldConf['config'], $curVersion, $swapVersion);
                                         }
                                     }
                                     unset($swapVersion['uid']);
                                     // Modify online version to become offline:
                                     unset($curVersion['uid']);
                                     $curVersion['pid'] = -1;
                                     // Set pid for OFFLINE
                                     $curVersion['t3ver_oid'] = intval($id);
                                     $curVersion['t3ver_wsid'] = $swapIntoWS ? intval($tmp_wsid) : 0;
                                     $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
                                     $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
                                     // Increment lifecycle counter
                                     $curVersion['t3ver_stage'] = 0;
                                     if (!$swapIntoWS) {
                                         $curVersion['t3ver_state'] = 0;
                                     }
                                     if ($table === 'pages') {
                                         // Keeping the swapmode state
                                         $curVersion['t3ver_swapmode'] = $swapVersion['t3ver_swapmode'];
                                     }
                                     // Registering and swapping MM relations in current and swap records:
                                     $this->version_remapMMForVersionSwap($table, $id, $swapWith);
                                     // Generating proper history data to prepare logging
                                     $this->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
                                     $this->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
                                     // Execute swapping:
                                     $sqlErrors = array();
                                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $swapVersion);
                                     if ($GLOBALS['TYPO3_DB']->sql_error()) {
                                         $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
                                     } else {
                                         $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($swapWith), $curVersion);
                                         if ($GLOBALS['TYPO3_DB']->sql_error()) {
                                             $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
                                         } else {
                                             unlink($lockFileName);
                                         }
                                     }
                                     if (!count($sqlErrors)) {
                                         // If a moving operation took place...:
                                         if ($movePlhID) {
                                             if (!$swapIntoWS) {
                                                 // Remove, if normal publishing:
                                                 $this->deleteEl($table, $movePlhID, TRUE, TRUE);
                                                 // For delete + completely delete!
                                             } else {
                                                 // Otherwise update the movePlaceholder:
                                                 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh);
                                                 $this->updateRefIndex($table, $movePlhID);
                                             }
                                         }
                                         // Checking for delete:
                                         if (!$swapIntoWS && ((int) $t3ver_state['swapVersion'] === 1 || (int) $t3ver_state['swapVersion'] === 2)) {
                                             // Delete only if new/deleted placeholders are there.
                                             $this->deleteEl($table, $id, TRUE);
                                             // Force delete
                                         }
                                         $this->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
                                         // Update reference index of the live record:
                                         $this->updateRefIndex($table, $id);
                                         // Set log entry for live record:
                                         $propArr = $this->getRecordPropertiesFromRow($table, $swapVersion);
                                         if ($propArr['_ORIG_pid'] == -1) {
                                             $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
                                         } else {
                                             $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
                                         }
                                         $theLogId = $this->log($table, $id, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
                                         $this->setHistory($table, $id, $theLogId);
                                         // Update reference index of the offline record:
                                         $this->updateRefIndex($table, $swapWith);
                                         // Set log entry for offline record:
                                         $propArr = $this->getRecordPropertiesFromRow($table, $curVersion);
                                         if ($propArr['_ORIG_pid'] == -1) {
                                             $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
                                         } else {
                                             $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
                                         }
                                         $theLogId = $this->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $swapWith), $propArr['event_pid']);
                                         $this->setHistory($table, $swapWith, $theLogId);
                                         // SWAPPING pids for subrecords:
                                         if ($table == 'pages' && $swapVersion['t3ver_swapmode'] >= 0) {
                                             // Collect table names that should be copied along with the tables:
                                             foreach ($TCA as $tN => $tCfg) {
                                                 if ($swapVersion['t3ver_swapmode'] > 0 || $TCA[$tN]['ctrl']['versioning_followPages']) {
                                                     // For "Branch" publishing swap ALL, otherwise for "page" publishing, swap only "versioning_followPages" tables
                                                     $temporaryPid = -($id + 1000000);
                                                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($id), array('pid' => $temporaryPid));
                                                     if ($GLOBALS['TYPO3_DB']->sql_error()) {
                                                         $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
                                                     }
                                                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($swapWith), array('pid' => $id));
                                                     if ($GLOBALS['TYPO3_DB']->sql_error()) {
                                                         $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
                                                     }
                                                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($temporaryPid), array('pid' => $swapWith));
                                                     if ($GLOBALS['TYPO3_DB']->sql_error()) {
                                                         $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
                                                     }
                                                     if (count($sqlErrors)) {
                                                         $this->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
                                                     }
                                                 }
                                             }
                                         }
                                         // Clear cache:
                                         $this->clear_cache($table, $id);
                                         // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
                                         if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
                                             $this->deleteEl($table, $swapWith, TRUE, TRUE);
                                             // For delete + completely delete!
                                         }
                                     } else {
                                         $this->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
                                     }
                                 } else {
                                     $this->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);
                                 }
                             } else {
                                 $this->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);
                             }
                         } else {
                             $this->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
                         }
                     } else {
                         $this->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
                     }
                 } else {
                     $this->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
                 }
             } else {
                 $this->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
             }
         } else {
             $this->newlog('Error: Either online or swap version could not be selected!', 2);
         }
     } else {
         $this->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
     }
 }