Index: t3lib/class.t3lib_loaddbgroup.php =================================================================== --- t3lib/class.t3lib_loaddbgroup.php (Revision 38989) +++ t3lib/class.t3lib_loaddbgroup.php (Arbeitskopie) @@ -101,6 +101,10 @@ var $MM_insert_fields = array(); // array of fields and value pairs used for insert in MM table var $MM_table_where = ''; // extra MM table where + /** + * @var boolean + */ + protected $updateReferenceIndex = TRUE; /** * Initialization of the class. @@ -190,6 +194,16 @@ } /** + * Sets whether the reference index shall be updated. + * + * @param boolean $updateReferenceIndex Whether the reference index shall be updated + * @return void + */ + public function setUpdateReferenceIndex($updateReferenceIndex) { + $this->updateReferenceIndex = (bool)$updateReferenceIndex; + } + + /** * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records. * * @param string Item list @@ -547,6 +561,12 @@ $whereClause .= ' AND '.$foreign_table_field.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table); } + // Select children in the same workspace: + if (t3lib_BEfunc::isTableWorkspaceEnabled($this->currentTable) && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table)) { + $currentRecord = t3lib_BEfunc::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause); + $whereClause .= t3lib_BEfunc::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']); + } + // get the correct sorting field if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field if ($conf['symmetric_sortby'] && $conf['symmetric_field']) { @@ -804,11 +824,14 @@ * * @param string Table name * @param integer Record UID - * @return void + * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable() */ function updateRefIndex($table,$id) { - $refIndexObj = t3lib_div::makeInstance('t3lib_refindex'); - $result = $refIndexObj->updateRefIndexTable($table,$id); + if ($this->updateReferenceIndex === TRUE) { + /** @var $refIndexObj t3lib_refindex */ + $refIndexObj = t3lib_div::makeInstance('t3lib_refindex'); + return $refIndexObj->updateRefIndexTable($table,$id); + } } /** Index: t3lib/class.t3lib_tcemain.php =================================================================== --- t3lib/class.t3lib_tcemain.php (Revision 38989) +++ t3lib/class.t3lib_tcemain.php (Arbeitskopie) @@ -339,6 +339,9 @@ var $copyMappingArray = Array(); // Used by the copy action to track the ids of new pages so subpages are correctly inserted! THIS is internally cleared for each executed copy operation! DO NOT USE THIS FROM OUTSIDE! Read from copyMappingArray_merged instead which is accumulating this information. var $remapStack = array(); // array used for remapping uids and values at the end of process_datamap var $remapStackRecords = array(); // array used for remapping uids and values at the end of process_datamap (e.g. $remapStackRecords[][] = ) + protected $remapStackChildIds = array(); // array used for checking whether new children need to be remapped + protected $remapStackActions = array(); // array used for executing addition actions after remapping happened (sett processRemapStack()) + protected $remapStackRefIndex = array(); // array used for executing post-processing on the reference index var $updateRefIndexStack = array(); // array used for additional calls to $this->updateRefIndex var $callFromImpExp = false; // tells, that this TCEmain was called from tx_impext - this variable is set by tx_impexp var $newIndexMap = array(); // Array for new flexform index mapping @@ -913,7 +916,13 @@ } $phShadowId = $this->insertDB($table,$id,$fieldArray,TRUE,0,TRUE); // When inserted, $this->substNEWwithIDs[$id] will be changed to the uid of THIS version and so the interface will pick it up just nice! if ($phShadowId) { - $this->placeholderShadowing($table,$phShadowId); + // Processes fields of the placeholder record: + $this->triggerRemapAction( + $table, + $id, + array($this, 'placeholderShadowing'), + array($table, $phShadowId) + ); // Hold auto-versionized ids of placeholders: $this->autoVersionIdMap[$table][$this->substNEWwithIDs[$id]] = $phShadowId; } @@ -1535,6 +1544,7 @@ // check, if there is a NEW... id in the value, that should be substituded later if (strpos($value, 'NEW') !== false) { $this->remapStackRecords[$table][$id] = array('remapStackIndex' => count($this->remapStack)); + $this->addNewValuesToRemapStackChildIds($valueArray); $this->remapStack[] = array( 'func' => 'checkValue_group_select_processDBdata', 'args' => array($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field), @@ -1945,6 +1955,7 @@ // We need to decide whether we use the stack or can save the relation directly. if(strpos($value, 'NEW') !== false || !t3lib_div::testInt($id)) { $this->remapStackRecords[$table][$id] = array('remapStackIndex' => count($this->remapStack)); + $this->addNewValuesToRemapStackChildIds($valueArray); $this->remapStack[] = array( 'func' => 'checkValue_inline_processDBdata', 'args' => array($valueArray, $tcaFieldConf, $id, $status, $table, $field), @@ -2565,6 +2576,9 @@ $this->accumulateForNotifEmail = array(); // Reset notification array + // Resolve dependencies of version/workspaces actions: + $this->cmdmap = $this->getCommandMap($this->cmdmap)->process()->get(); + // Traverse command map: foreach (array_keys($this->cmdmap) as $table) { @@ -2625,22 +2639,7 @@ } break; case 'swap': - $swapMode = $this->BE_USER->getTSConfigVal('options.workspaces.swapMode'); - $elementList = array(); - if ($swapMode == 'any' || ($swapMode == 'page' && $table == 'pages')) { - // check if we are allowed to do synchronios publish. We must have a single element in the cmdmap to be allowed - if (count($this->cmdmap) == 1 && count($this->cmdmap[$table]) == 1) { - $elementList = $this->findPageElementsForVersionSwap($table, $id, $value['swapWith']); - } - } - if (count($elementList) == 0) { - $elementList[$table][] = array($id, $value['swapWith']); - } - foreach ($elementList as $tbl => $idList) { - foreach ($idList as $idKey => $idSet) { - $this->version_swap($tbl,$idSet[0],$idSet[1],$value['swapIntoWS']); - } - } + $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS']); break; case 'clearWSID': $this->version_clearWSID($table,$id); @@ -2649,37 +2648,12 @@ $this->version_clearWSID($table,$id,TRUE); break; case 'setStage': - $elementList = array(); - $idList = $elementList[$table] = t3lib_div::trimExplode(',',$id,1); - $setStageMode = $this->BE_USER->getTSConfigVal('options.workspaces.changeStageMode'); - if ($setStageMode == 'any' || $setStageMode == 'page') { - if (count($idList) == 1) { - $rec = t3lib_BEfunc::getRecord($table, $idList[0], 't3ver_wsid'); - $workspaceId = $rec['t3ver_wsid']; - } - else { - $workspaceId = $this->BE_USER->workspace; - } - if ($table !== 'pages') { - if ($setStageMode == 'any') { - // (1) Find page to change stage and (2) find other elements from the same ws to change stage - $pageIdList = array(); - $this->findPageIdsForVersionStateChange($table, $idList, $workspaceId, $pageIdList, $elementList); - $this->findPageElementsForVersionStageChange($pageIdList, $workspaceId, $elementList); - } - } - else { - // Find all elements from the same ws to change stage - $this->findRealPageIds($idList); - $this->findPageElementsForVersionStageChange($idList, $workspaceId, $elementList); - } + $elementIds = t3lib_div::trimExplode(',', $id, TRUE); + foreach ($elementIds as $elementId) { + $this->version_setStage($table, $elementId, $value['stageId'], + (isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment), + TRUE); } - - foreach ($elementList as $tbl => $elementIdList) { - foreach($elementIdList as $elementId) { - $this->version_setStage($tbl,$elementId,$value['stageId'],$value['comment']?$value['comment']:$this->generalComment, TRUE); - } - } break; } break; @@ -2705,6 +2679,7 @@ } // Finally, before exit, check if there are ID references to remap. This might be the case if versioning or copying has taken place! + $this->processRemapStack(); $this->remapListedDBRecords(); @@ -2852,6 +2827,10 @@ if ($theNewSQLID) { $this->copyRecord_fixRTEmagicImages($table, t3lib_BEfunc::wsMapId($table, $theNewSQLID)); $this->copyMappingArray[$table][$origUid] = $theNewSQLID; + // Keep automatically versionized record information: + if (isset($copyTCE->autoVersionIdMap[$table][$theNewSQLID])) { + $this->autoVersionIdMap[$table][$theNewSQLID] = $copyTCE->autoVersionIdMap[$table][$theNewSQLID]; + } } // Copy back the cached TSconfig @@ -3188,8 +3167,17 @@ } else { if (!t3lib_div::testInt($realDestPid)) { $newId = $this->copyRecord($v['table'], $v['id'], -$v['id']); - } elseif ($realDestPid == -1) { - $newId = $this->versionizeRecord($v['table'], $v['id'], 'Auto-created for WS #'.$this->BE_USER->workspace); + } elseif ($realDestPid == -1 && t3lib_BEfunc::isTableWorkspaceEnabled($v['table'])) { + $workspaceVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord( + $this->BE_USER->workspace, $v['table'], $v['id'], 'uid' + ); + // If workspace version does not exist, create a new one: + if ($workspaceVersion === FALSE) { + $newId = $this->versionizeRecord($v['table'], $v['id'], 'Auto-created for WS #' . $this->BE_USER->workspace); + // If workspace version already exists, use it: + } else { + $newId = $workspaceVersion['uid']; + } } else { $newId = $this->copyRecord_raw($v['table'], $v['id'], $realDestPid); } @@ -3933,6 +3921,16 @@ // Execute the copy: $newId = $this->copyRecord($table, $uid, -$uid, 1, $overrideValues, implode(',', $excludeFields), $language); + $autoVersionNewId = $this->getAutoVersionId($table, $newId); + if (is_null($autoVersionNewId) === FALSE) { + $this->triggerRemapAction( + $table, + $newId, + array($this, 'placeholderShadowing'), + array($table, $autoVersionNewId), + TRUE + ); + } } else { // Create new record: @@ -4027,10 +4025,12 @@ if (t3lib_div::testInt($type) && isset($elementsOriginal[$type])) { $item = $elementsOriginal[$type]; $item['id'] = $this->localize($item['table'], $item['id'], $language); + $item['id'] = $this->overlayAutoVersionId($item['table'], $item['id']); $dbAnalysisCurrent->itemArray[] = $item; } elseif (t3lib_div::inList('localize,synchronize', $type)) { foreach ($elementsOriginal as $originalId => $item) { $item['id'] = $this->localize($item['table'], $item['id'], $language); + $item['id'] = $this->overlayAutoVersionId($item['table'], $item['id']); $dbAnalysisCurrent->itemArray[] = $item; } } @@ -4695,6 +4695,8 @@ $verTablesArray[] = $tN; } } + // Remove the possible inline child tables from the tables to be versioniozed automatically: + $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages')); // Begin to copy pages if we're allowed to: if ($this->admin || in_array('pages',$allowedTablesArray)) { @@ -4874,7 +4876,7 @@ $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); + $this->addRemapStackRefIndex($table, $movePlhID); } } @@ -4886,7 +4888,7 @@ $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); + $this->addRemapStackRefIndex($table, $id); // Set log entry for live record: $propArr = $this->getRecordPropertiesFromRow($table, $swapVersion); if ( $propArr['_ORIG_pid'] == -1) { @@ -4898,7 +4900,7 @@ $this->setHistory($table, $id, $theLogId); // Update reference index of the offline record: - $this->updateRefIndex($table,$swapWith); + $this->addRemapStackRefIndex($table, $swapWith); // Set log entry for offline record: $propArr = $this->getRecordPropertiesFromRow($table, $curVersion); if ( $propArr['_ORIG_pid'] == -1) { @@ -5046,16 +5048,25 @@ // Process pointer fields on normalized database: if ($inlineType == 'field') { - // Read relations that point to the current record (e.g. live record): + // Read relations that point to the current record (e.g. live record): + /** @var $dbAnalysisCur t3lib_loadDBGroup */ $dbAnalysisCur = t3lib_div::makeInstance('t3lib_loadDBGroup'); + $dbAnalysisCur->setUpdateReferenceIndex(FALSE); $dbAnalysisCur->start('', $conf['foreign_table'], '', $curVersion['uid'], $table, $conf); - // Read relations that point to the record to be swapped with e.g. draft record): + // Read relations that point to the record to be swapped with e.g. draft record): + /** @var $dbAnalysisSwap t3lib_loadDBGroup */ $dbAnalysisSwap = t3lib_div::makeInstance('t3lib_loadDBGroup'); + $dbAnalysisSwap->setUpdateReferenceIndex(FALSE); $dbAnalysisSwap->start('', $conf['foreign_table'], '', $swapVersion['uid'], $table, $conf); // Update relations for both (workspace/versioning) sites: $dbAnalysisCur->writeForeignField($conf,$curVersion['uid'],$swapVersion['uid']); $dbAnalysisSwap->writeForeignField($conf,$swapVersion['uid'],$curVersion['uid']); + $items = array_merge($dbAnalysisCur->itemArray, $dbAnalysisSwap->itemArray); + foreach ($items as $item) { + $this->addRemapStackRefIndex($item['table'], $item['id']); + } + // Swap field values (CSV): // BUT: These values will be swapped back in the next steps, when the *CHILD RECORD ITSELF* is swapped! } elseif ($inlineType == 'list') { @@ -5212,7 +5223,6 @@ - /********************************************* * * Cmd: Helper functions @@ -5385,9 +5395,15 @@ $this->remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table); } elseif ($inlineType !== false) { + /** @var $dbAnalysis t3lib_loadDBGroup */ $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup'); $dbAnalysis->start($value, $conf['foreign_table'], '', 0, $table, $conf); + // Update child records if using pointer fields ('foreign_field'): + if ($inlineType == 'field') { + $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate); + } + // If the current field is set on a page record, update the pid of related child records: if ($table == 'pages') { $thePidToUpdate = $theUidToUpdate; @@ -5397,16 +5413,11 @@ $thePidToUpdate = $this->copyMappingArray_merged['pages'][$thePidToUpdate]; } - // Update child records if using pointer fields ('foreign_field'): - if ($inlineType == 'field') { - $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate); - } - - // Update child records if change to pid is required: + // Update child records if change to pid is required (only if the current record is not on a workspace): if ($thePidToUpdate) { $updateValues = array('pid' => $thePidToUpdate); foreach ($dbAnalysis->itemArray as $v) { - if ($v['id'] && $v['table']) { + if ($v['id'] && $v['table'] && is_null(t3lib_BEfunc::getLiveVersionIdOfRecord($v['table'], $v['id']))) { $GLOBALS['TYPO3_DB']->exec_UPDATEquery($v['table'], 'uid='.intval($v['id']), $updateValues); } } @@ -5422,6 +5433,7 @@ * @return void */ function processRemapStack() { + // Processes the remap stack: if(is_array($this->remapStack)) { foreach($this->remapStack as $remapAction) { // If no position index for the arguments was set, skip this remap action: @@ -5491,12 +5503,75 @@ } } } + // Processes the remap stack actions: + if ($this->remapStackActions) { + foreach ($this->remapStackActions as $action) { + if (isset($action['callback']) && isset($action['arguments'])) { + call_user_func_array( + $action['callback'], + $action['arguments'] + ); + } + } + } + // Processes the reference index updates of the remap stack: + foreach ($this->remapStackRefIndex as $table => $idArray) { + foreach ($idArray as $id) { + $this->updateRefIndex($table, $id); + unset($this->remapStackRefIndex[$table][$id]); + } + } // Reset: $this->remapStack = array(); $this->remapStackRecords = array(); + $this->remapStackActions = array(); + $this->remapStackRefIndex = array(); } /** + * Triggers a remap action for a specific record. + * + * Some records are post-processed by the processRemapStack() method (e.g. IRRE children). + * This method determines wether an action/modification is executed directly to a record + * or is postponed to happen after remapping data. + * + * @param string $table Name of the table + * @param string $id Id of the record (can also be a "NEW..." string) + * @param array $callback The method to be called + * @param array $arguments The arguments to be submitted to the callback method + * @param boolean $forceRemapStackActions Whether to force to use the stack + * @return void + * + * @see processRemapStack + */ + protected function triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions = FALSE) { + // Check whether the affected record is marked to be remapped: + if (!$forceRemapStackActions && !isset($this->remapStackRecords[$table][$id]) && !isset($this->remapStackChildIds[$id])) { + call_user_func_array($callback, $arguments); + } else { + $this->remapStackActions[] = array( + 'affects' => array( + 'table' => $table, + 'id' => $id, + ), + 'callback' => $callback, + 'arguments' => $arguments, + ); + } + } + + /** + * Adds a table-id-pair to the reference index remapping stack. + * + * @param string $table + * @param integer $id + * @return void + */ + protected function addRemapStackRefIndex($table, $id) { + $this->remapStackRefIndex[$table][$id] = $id; + } + + /** * If a parent record was versionized on a workspace in $this->process_datamap, * it might be possible, that child records (e.g. on using IRRE) were affected. * This function finds these relations and updates their uids in the $incomingFieldArray. @@ -7819,6 +7893,88 @@ } return $result; } + + /** + * Gets all possible child tables that are used on each parent table as field. + * + * @param string $parentTable + * @param array $possibleInlineChildren + * @return array + */ + protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) { + t3lib_div::loadTCA($parentTable); + + foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) { + if (isset($parentFieldDefinition['config']['type'])) { + $parentFieldConfiguration = $parentFieldDefinition['config']; + if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) { + if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) { + $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable( + $parentFieldConfiguration['foreign_table'], + array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table']) + ); + } + } + } + } + + return $possibleInlineChildren; + } + + /** + * Gets the automatically versionized id of a record. + * + * @param string $table Name of the table + * @param integer $id Uid of the record + * @return integer + */ + protected function getAutoVersionId($table, $id) { + $result = NULL; + + if (isset($this->autoVersionIdMap[$table][$id])) { + $result = $this->autoVersionIdMap[$table][$id]; + } + + return $result; + } + + /** + * Overlays the automatically versionized id of a record. + * + * @param string $table Name of the table + * @param integer $id Uid of the record + * @return integer + */ + protected function overlayAutoVersionId($table, $id) { + $autoVersionId = $this->getAutoVersionId($table, $id); + + if (is_null($autoVersionId) === FALSE) { + $id = $autoVersionId; + } + + return $id; + } + + /** + * Adds new values to the remapStackChildIds array. + * + * @param array $idValues uid values + * @return void + */ + protected function addNewValuesToRemapStackChildIds(array $idValues) { + foreach ($idValues as $idValue) { + if (strpos($idValue, 'NEW') === 0) { + $this->remapStackChildIds[$idValue] = TRUE; + } + } + } + + /** + * @return t3lib_TCEmain_CommandMap + */ + protected function getCommandMap(array $commandMap) { + return t3lib_div::makeInstance('t3lib_TCEmain_CommandMap', $this, $commandMap); + } } Index: t3lib/class.t3lib_tcemain_commandmap.php =================================================================== --- t3lib/class.t3lib_tcemain_commandmap.php (Revision 0) +++ t3lib/class.t3lib_tcemain_commandmap.php (Revision 40148) @@ -0,0 +1,745 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Handles the t3lib_TCEmain command map and is only used in combination with t3lib_TCEmain. + */ +class t3lib_TCEmain_CommandMap { + const SCOPE_WorkspacesSwap = 'SCOPE_WorkspacesSwap'; + const SCOPE_WorkspacesSetStage = 'SCOPE_WorkspacesSetStage'; + + const KEY_ScopeErrorMessage = 'KEY_ScopeErrorMessage'; + const KEY_ScopeErrorCode = 'KEY_ScopeErrorCode'; + const KEY_GetElementPropertiesCallback = 'KEY_GetElementPropertiesCallback'; + const KEY_GetCommonPropertiesCallback = 'KEY_GetCommonPropertiesCallback'; + const KEY_ElementConstructCallback = 'KEY_EventConstructCallback'; + const KEY_ElementCreateChildReferenceCallback = 'KEY_ElementCreateChildReferenceCallback'; + const KEY_ElementCreateParentReferenceCallback = 'KEY_ElementCreateParentReferenceCallback'; + const KEY_PurgeWithErrorMessageGetIdCallback = 'KEY_PurgeWithErrorMessageGetIdCallback'; + const KEY_UpdateGetIdCallback = 'KEY_UpdateGetIdCallback'; + const KEY_TransformDependentElementsToUseLiveId = 'KEY_TransformDependentElementsToUseLiveId'; + + /** + * @var t3lib_TCEmain + */ + protected $parent; + + /** + * @var array + */ + protected $commandMap = array(); + + /** + * @var string + */ + protected $workspacesSwapMode; + + /** + * @var string + */ + protected $workspacesChangeStageMode; + + /** + * @var boolean + */ + protected $workspacesConsiderReferences; + + /** + * @var array + */ + protected $scopes; + + /** + * Creates this object. + * + * @param t3lib_TCEmain $parent + * @param array $commandMap + */ + public function __construct(t3lib_TCEmain $parent, array $commandMap) { + $this->setParent($parent); + $this->set($commandMap); + + $this->setWorkspacesSwapMode($parent->BE_USER->getTSConfigVal('options.workspaces.swapMode')); + $this->setWorkspacesChangeStageMode($parent->BE_USER->getTSConfigVal('options.workspaces.changeStageMode')); + $this->setWorkspacesConsiderReferences($parent->BE_USER->getTSConfigVal('options.workspaces.considerReferences')); + + $this->constructScopes(); + } + + /** + * Gets the command map. + * + * @return array + */ + public function get() { + return $this->commandMap; + } + + /** + * Sets the command map. + * + * @param array $commandMap + * @return t3lib_TCEmain_CommandMap + */ + public function set(array $commandMap) { + $this->commandMap = $commandMap; + return $this; + } + + /** + * Gets the parent object. + * + * @return t3lib_TCEmain + */ + public function getParent() { + return $this->parent; + } + + /** + * Sets the parent object. + * + * @param t3lib_TCEmain $parent + * @return t3lib_TCEmain_CommandMap + */ + public function setParent(t3lib_TCEmain $parent) { + $this->parent = $parent; + return $this; + } + + /** + * Sets the workspaces swap mode + * (see options.workspaces.swapMode). + * + * @param string $workspacesSwapMode + * @return t3lib_TCEmain_CommandMap + */ + public function setWorkspacesSwapMode($workspacesSwapMode) { + $this->workspacesSwapMode = (string)$workspacesSwapMode; + return $this; + } + + /** + * Sets the workspaces change stage mode + * see options.workspaces.changeStageMode) + * + * @param string $workspacesChangeStageMode + * @return t3lib_TCEmain_CommandMap + */ + public function setWorkspacesChangeStageMode($workspacesChangeStageMode) { + $this->workspacesChangeStageMode = (string)$workspacesChangeStageMode; + return $this; + } + + /** + * Sets the workspace behaviour to automatically consider references + * (see options.workspaces.considerReferences) + * + * @param boolean $workspacesConsiderReferences + * @return t3lib_TCEmain_CommandMap + */ + public function setWorkspacesConsiderReferences($workspacesConsiderReferences) { + $this->workspacesConsiderReferences = (bool)$workspacesConsiderReferences; + return $this; + } + + /** + * Processes the command map. + * + * @return t3lib_TCEmain_CommandMap + */ + public function process() { + $this->resolveWorkspacesSwapDependencies(); + $this->resolveWorkspacesSetStageDependencies(); + return $this; + } + + /** + * Resolves workspaces related dependencies for swapping/publishing of the command map. + * Workspaces records that have children or (relative) parents which are versionized + * but not published with this request, are removed from the command map. Otherwise + * this would produce hanging record sets and lost references. + * + * @return void + */ + protected function resolveWorkspacesSwapDependencies() { + $scope = self::SCOPE_WorkspacesSwap; + $dependency = $this->getDependencyUtility($scope); + + foreach ($this->commandMap as $table => $liveIdCollection) { + foreach ($liveIdCollection as $liveId => $commandCollection) { + foreach ($commandCollection as $command => $properties) { + if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'swap') { + if (isset($properties['swapWith']) && t3lib_div::testInt($properties['swapWith'])) { + $this->addWorkspacesSwapElements($dependency, $table, $liveId, $properties); + } + } + } + } + } + + $this->applyWorkspacesDependencies($dependency, $scope); + } + + /** + * Adds workspaces elements for swapping/publishing and takes care of the swapMode. + * + * @param t3lib_utility_Dependency $dependency + * @param string $table + * @param iteger $liveId + * @param array $properties + * @return void + */ + protected function addWorkspacesSwapElements(t3lib_utility_Dependency $dependency, $table, $liveId, array $properties) { + $elementList = array(); + + // Fetch accordant elements if the swapMode is 'any' or 'pages': + if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') { + $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']); + } + + foreach ($elementList as $elementTable => $elementIdArray) { + foreach ($elementIdArray as $elementIds) { + $dependency->addElement( + $elementTable, $elementIds[1], + array('liveId' => $elementIds[0], 'properties' => array_merge($properties, array('swapWith' => $elementIds[1]))) + ); + } + } + + if (count($elementList) === 0) { + $dependency->addElement( + $table, $properties['swapWith'], array('liveId' => $liveId, 'properties' => $properties) + ); + } + } + + /** + * Resolves workspaces related dependencies for staging of the command map. + * Workspaces records that have children or (relative) parents which are versionized + * but not staged with this request, are removed from the command map. + * + * @return void + */ + protected function resolveWorkspacesSetStageDependencies() { + $scope = self::SCOPE_WorkspacesSetStage; + $dependency = $this->getDependencyUtility($scope); + + foreach ($this->commandMap as $table => $liveIdCollection) { + foreach ($liveIdCollection as $liveIdList => $commandCollection) { + foreach ($commandCollection as $command => $properties) { + if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'setStage') { + if (isset($properties['stageId']) && t3lib_div::testInt($properties['stageId'])) { + $this->addWorkspacesSetStageElements($dependency, $table, $liveIdList, $properties); + $this->explodeSetStage($table, $liveIdList, $properties); + } + } + } + } + } + + $this->applyWorkspacesDependencies($dependency, $scope); + } + + /** + * Adds workspaces elements for staging and takes care of the changeStageMode. + * + * @param t3lib_utility_Dependency $dependency + * @param string $table + * @param string $liveIdList + * @param array $properties + * @return void + */ + protected function addWorkspacesSetStageElements(t3lib_utility_Dependency $dependency, $table, $liveIdList, array $properties) { + $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE); + $elementList = array($table => $liveIds); + + if (t3lib_div::inList('any,pages', $this->workspacesChangeStageMode)) { + if (count($liveIds) === 1) { + $workspaceRecord = t3lib_BEfunc::getRecord($table, $liveIds[0], 't3ver_wsid'); + $workspaceId = $workspaceRecord['t3ver_wsid']; + } else { + $workspaceId = $this->getParent()->BE_USER->workspace; + } + + if ($table === 'pages') { + // Find all elements from the same ws to change stage + $this->getParent()->findRealPageIds($liveIds); + $this->getParent()->findPageElementsForVersionStageChange($liveIds, $workspaceId, $elementList); + } elseif ($this->workspacesChangeStageMode === 'any') { + // Find page to change stage: + $pageIdList = array(); + $this->getParent()->findPageIdsForVersionStateChange($table, $liveIds, $workspaceId, $pageIdList, $elementList); + // Find other elements from the same ws to change stage: + $this->getParent()->findPageElementsForVersionStageChange($pageIdList, $workspaceId, $elementList); + } + } + + foreach ($elementList as $elementTable => $elementIds) { + foreach($elementIds as $elementId) { + $dependency->addElement( + $elementTable, $elementId, + array('properties' => $properties) + ); + } + } + } + + /** + * Explodes id-lists in the command map for staging actions. + * + * @throws RuntimeException + * @param string $table + * @param string $liveIdList + * @param array $properties + * @return void + */ + protected function explodeSetStage($table, $liveIdList, array $properties) { + $extractedCommandMap = array(); + $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE); + + if (count($liveIds) > 1) { + foreach ($liveIds as $liveId) { + if (isset($this->commandMap[$table][$liveId]['version'])) { + throw new RuntimeException('Command map for [' . $table . '][' . $liveId . '][version] was already set.', 1289391048); + } + + $extractedCommandMap[$table][$liveId]['version'] = $properties; + } + + $this->remove($table, $liveIdList, 'version'); + $this->mergeToBottom($extractedCommandMap); + } + } + + /** + * Applies the workspaces dependencies and removes incomplete structures or automatically + * completes them, depending on the options.workspaces.considerReferences setting + * + * @param t3lib_utility_Dependency $dependency + * @param string $scope + * @return void + */ + protected function applyWorkspacesDependencies(t3lib_utility_Dependency $dependency, $scope) { + $transformDependentElementsToUseLiveId = $this->getScopeData($scope, self::KEY_TransformDependentElementsToUseLiveId); + + $elementsToBeVersionized = $dependency->getElements(); + if ($transformDependentElementsToUseLiveId) { + $elementsToBeVersionized = $this->transformDependentElementsToUseLiveId($elementsToBeVersionized); + } + + $outerMostParents = $dependency->getOuterMostParents(); + /** @var $outerMostParent t3lib_utility_Dependency_Element */ + foreach ($outerMostParents as $outerMostParent) { + $dependentElements = $dependency->getNestedElements($outerMostParent); + if ($transformDependentElementsToUseLiveId) { + $dependentElements = $this->transformDependentElementsToUseLiveId($dependentElements); + } + + $intersectingElements = array_intersect_key($dependentElements, $elementsToBeVersionized); + + if (count($intersectingElements) > 0) { + // If at least one element intersects but not all, throw away all elements of the depdendent structure: + if (count($intersectingElements) !== count($dependentElements) && $this->workspacesConsiderReferences === FALSE) { + $this->purgeWithErrorMessage($intersectingElements, $scope); + // If everything is fine or references shall be considered automatically: + } else { + $this->update(current($intersectingElements), $dependentElements, $scope); + } + } + } + } + + /** + * Purges incomplete structures from the command map and triggers an error message. + * + * @param array $elements + * @param string $scope + * @return void + */ + protected function purgeWithErrorMessage(array $elements, $scope) { + /** @var $dependentElement t3lib_utility_Dependency_Element */ + foreach ($elements as $element) { + $table = $element->getTable(); + $id = $this->processCallback( + $this->getScopeData($scope, self::KEY_PurgeWithErrorMessageGetIdCallback), + array($element) + ); + + $this->remove($table, $id, 'version'); + $this->getParent()->log( + $table, $id, + 5, 0, 1, + $this->getScopeData($scope, self::KEY_ScopeErrorMessage), + $this->getScopeData($scope, self::KEY_ScopeErrorCode), + array( + t3lib_BEfunc::getRecordTitle($table, t3lib_BEfunc::getRecord($table, $id)), + $table, $id + ) + ); + } + } + + /** + * Updates the command map accordant to valid structures and takes care of the correct order. + * + * @param t3lib_utility_Dependency_Element $intersectingElement + * @param array $elements + * @param string $scope + * @return void + */ + protected function update(t3lib_utility_Dependency_Element $intersectingElement, array $elements, $scope) { + $orderedCommandMap = array(); + + $commonProperties = $this->processCallback( + $this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback), + array($intersectingElement) + ); + + /** @var $dependentElement t3lib_utility_Dependency_Element */ + foreach ($elements as $element) { + $table = $element->getTable(); + $id = $this->processCallback( + $this->getScopeData($scope, self::KEY_UpdateGetIdCallback), + array($element) + ); + + $this->remove($table, $id, 'version'); + $orderedCommandMap[$table][$id]['version'] = array_merge( + $commonProperties, + $this->processCallback( + $this->getScopeData($scope, self::KEY_GetElementPropertiesCallback), + array($element) + ) + ); + } + + // Ensure that ordered command map is on top of the command map: + $this->mergeToTop($orderedCommandMap); + } + + /** + * Merges command map elements to the top of the current command map.. + * + * @param array $commandMap + * @return void + */ + protected function mergeToTop(array $commandMap) { + $this->commandMap = t3lib_div::array_merge_recursive_overrule($commandMap, $this->commandMap); + } + + /** + * Merges command map elements to the bottom of the current command map. + * + * @param array $commandMap + * @return void + */ + protected function mergeToBottom(array $commandMap) { + $this->commandMap = t3lib_div::array_merge_recursive_overrule($this->commandMap, $commandMap); + } + + /** + * Removes an element from the command map. + * + * @param string $table + * @param string $id + * @param string $command (optional) + * @return void + */ + protected function remove($table, $id, $command = NULL) { + if (is_string($command)) { + unset($this->commandMap[$table][$id][$command]); + } else { + unset($this->commandMap[$table][$id]); + } + } + + /** + * Callback to get the liveId of an dependent element. + * + * @param t3lib_utility_Dependency_Element $element + * @return integer + */ + protected function getElementLiveIdCallback(t3lib_utility_Dependency_Element $element) { + return $element->getDataValue('liveId'); + } + + /** + * Callback to get the real id of an dependent element. + * + * @param t3lib_utility_Dependency_Element $element + * @return integer + */ + protected function getElementIdCallback(t3lib_utility_Dependency_Element $element) { + return $element->getId(); + } + + /** + * Callback to get the specific properties of a dependent element for swapping/publishing. + * + * @param t3lib_utility_Dependency_Element $element + * @return array + */ + protected function getElementSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) { + return array( + 'swapWith' => $element->getId(), + ); + } + + /** + * Callback to get common properties of dependent elements for swapping/publishing. + * + * @param t3lib_utility_Dependency_Element $element + * @return array + */ + protected function getCommonSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) { + $commonSwapProperties = array(); + + $elementProperties = $element->getDataValue('properties'); + if (isset($elementProperties['action'])) { + $commonSwapProperties['action'] = $elementProperties['action']; + } + if (isset($elementProperties['swapIntoWS'])) { + $commonSwapProperties['swapIntoWS'] = $elementProperties['swapIntoWS']; + } + + return $commonSwapProperties; + } + + /** + * Callback to get the specific properties of a dependent element for staging. + * + * @param t3lib_utility_Dependency_Element $element + * @return array + */ + protected function getElementSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) { + return $this->getCommonSetStagePropertiesCallback($element); + } + + /** + * Callback to get common properties of dependent elements for staging. + * + * @param t3lib_utility_Dependency_Element $element + * @return array + */ + protected function getCommonSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) { + $commonSetStageProperties = array(); + + $elementProperties = $element->getDataValue('properties'); + if (isset($elementProperties['stageId'])) { + $commonSetStageProperties['stageId'] = $elementProperties['stageId']; + } + if (isset($elementProperties['comment'])) { + $commonSetStageProperties['comment'] = $elementProperties['comment']; + } + + return $commonSetStageProperties; + } + + + /** + * Gets an instance of the depency resolver utility. + * + * @return t3lib_utility_Dependency + */ + protected function getDependencyUtility($scope) { + + /** @var $dependency t3lib_utility_Dependency */ + $dependency = t3lib_div::makeInstance('t3lib_utility_Dependency'); + $dependency->setOuterMostParentsRequireReferences(TRUE); + + if ($this->getScopeData($scope, self::KEY_ElementConstructCallback)) { + $dependency->setEventCallback( + t3lib_utility_Dependency_Element::EVENT_Construct, + $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementConstructCallback)) + ); + } + if ($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)) { + $dependency->setEventCallback( + t3lib_utility_Dependency_Element::EVENT_CreateChildReference, + $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)) + ); + } + if ($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)) { + $dependency->setEventCallback( + t3lib_utility_Dependency_Element::EVENT_CreateParentReference, + $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)) + ); + } + + return $dependency; + } + + /** + * Callback to determine whether a new child reference shall be considered in the dependency resolver utility. + * + * @param array $callerArguments + * @param array $targetArgument + * @param t3lib_utility_Dependency_Element $caller + * @param string $eventName + * @return string Skip response (if required) + */ + public function createNewDependentElementChildReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) { + /** @var $reference t3lib_utility_Dependency_Reference */ + $reference = $callerArguments['reference']; + + $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($caller->getTable(), $reference->getField()); + + if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getParent()->getInlineFieldType($fieldCOnfiguration))) { + return t3lib_utility_Dependency_Element::RESPONSE_Skip; + } + } + + /** + * Callback to determine whether a new parent reference shall be considered in the dependency resolver utility. + * + * @param array $callerArguments + * @param array $targetArgument + * @param t3lib_utility_Dependency_Element $caller + * @param string $eventName + * @return string Skip response (if required) + */ + public function createNewDependentElementParentReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) { + /** @var $reference t3lib_utility_Dependency_Reference */ + $reference = $callerArguments['reference']; + + $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($reference->getElement()->getTable(), $reference->getField()); + + if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getParent()->getInlineFieldType($fieldCOnfiguration))) { + return t3lib_utility_Dependency_Element::RESPONSE_Skip; + } + } + + /** + * Callback to add additional data to new elements created in the dependency resolver utility. + * + * @param t3lib_utility_Dependency_Element $caller + * @param array $callerArguments + * @param array $targetArgument + * @return void + */ + public function createNewDependentElementCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller) { + if ($caller->hasDataValue('liveId') === FALSE) { + $liveId = t3lib_BEfunc::getLiveVersionIdOfRecord($caller->getTable(), $caller->getId()); + if (is_null($liveId) === FALSE) { + $caller->setDataValue('liveId', $liveId); + } + } + } + + /** + * Transforms dependent elements to use the liveId as array key. + * + * @param array $elements Depedent elements, each of type t3lib_utility_Dependency_Element + * @return array + */ + protected function transformDependentElementsToUseLiveId(array $elements) { + $transformedElements = array(); + + /** @var $element t3lib_utility_Dependency_Element */ + foreach ($elements as $element) { + $elementName = t3lib_utility_Dependency_Element::getIdentifier( + $element->getTable(), $element->getDataValue('liveId') + ); + $transformedElements[$elementName] = $element; + } + + return $transformedElements; + } + + /** + * Constructs the scope settings. + * Currently the scopes for swapping/publishing and staging are available. + * + * @return void + */ + protected function constructScopes() { + $this->scopes = array( + self::SCOPE_WorkspacesSwap => array( + self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be swapped or published independently, because it is related to other new or modified records.', + self::KEY_ScopeErrorCode => 1288283630, + self::KEY_GetElementPropertiesCallback => 'getElementSwapPropertiesCallback', + self::KEY_GetCommonPropertiesCallback => 'getCommonSwapPropertiesCallback', + self::KEY_ElementConstructCallback => 'createNewDependentElementCallback', + self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback', + self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback', + self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementLiveIdCallback', + self::KEY_UpdateGetIdCallback => 'getElementLiveIdCallback', + self::KEY_TransformDependentElementsToUseLiveId => TRUE, + ), + self::SCOPE_WorkspacesSetStage => array( + self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be sent to another stage independently, because it is related to other new or modified records.', + self::KEY_ScopeErrorCode => 1289342524, + self::KEY_GetElementPropertiesCallback => 'getElementSetStagePropertiesCallback', + self::KEY_GetCommonPropertiesCallback => 'getCommonSetStagePropertiesCallback', + self::KEY_ElementConstructCallback => NULL, + self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback', + self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback', + self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementIdCallback', + self::KEY_UpdateGetIdCallback => 'getElementIdCallback', + self::KEY_TransformDependentElementsToUseLiveId => FALSE, + ), + ); + } + + /** + * Gets data for a particular scope. + * + * @throws RuntimeException + * @param string $scope + * @param string $key + * @return string + */ + protected function getScopeData($scope, $key) { + if (!isset($this->scopes[$scope])) { + throw new RuntimeException('Scope "' . $scope . '" is not defined.', 1289342187); + } + + return $this->scopes[$scope][$key]; + } + + /** + * Gets a new callback to be used in the dependency resolver utility. + * + * @param string $callbackMethod + * @param array $targetArguments + * @return t3lib_utility_Dependency_Callback + */ + protected function getDependencyCallback($method, array $targetArguments = array()) { + return t3lib_div::makeInstance('t3lib_utility_Dependency_Callback', $this, $method, $targetArguments); + } + + /** + * Processes a local callback inside this object. + * + * @param string $method + * @param array $callbackArguments + * @return mixed + */ + protected function processCallback($method, array $callbackArguments) { + return call_user_func_array(array($this, $method), $callbackArguments); + } +} Index: t3lib/utility/class.t3lib_utility_dependency.php =================================================================== --- t3lib/utility/class.t3lib_utility_dependency.php (Revision 0) +++ t3lib/utility/class.t3lib_utility_dependency.php (Revision 40148) @@ -0,0 +1,193 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Object to handle and determine dependent references of elements. + */ +class t3lib_utility_Dependency { + /** + * @var t3lib_utility_Dependency_Factory + */ + protected $factory; + + /** + * @var array + */ + protected $elements = array(); + + /** + * @var array + */ + protected $eventCallbacks = array(); + + /** + * @var boolean + */ + protected $outerMostParentsRequireReferences = FALSE; + + /** + * @var array + */ + protected $outerMostParents; + + /** + * Sets a callback for a particular event. + * + * @param string $eventName + * @param t3lib_utility_Dependency_Callback $callback + * @return t3lib_utility_Dependency + */ + public function setEventCallback($eventName, t3lib_utility_Dependency_Callback $callback) { + $this->eventCallbacks[$eventName] = $callback; + return $this; + } + + /** + * Executes a registered callback (if any) for a particular event. + * + * @param string $eventName + * @param object $caller + * @param array $callerArguments + * @return mixed + */ + public function executeEventCallback($eventName, $caller, array $callerArguments = array()) { + if (isset($this->eventCallbacks[$eventName])) { + /** @var $callback t3lib_utility_Dependency_Callback */ + $callback = $this->eventCallbacks[$eventName]; + return $callback->execute($callerArguments, $caller, $eventName); + } + } + + /** + * Sets the condition that outermost parents required at least one child or parent reference. + * + * @param boolean $outerMostParentsRequireReferences + * @return t3lib_utility_Dependency + */ + public function setOuterMostParentsRequireReferences($outerMostParentsRequireReferences) { + $this->outerMostParentsRequireReferences = (bool)$outerMostParentsRequireReferences; + return $this; + } + + /** + * Adds an element to be checked for dependent references. + * + * @param string $table + * @param integer $id + * @param array $data + * @return t3lib_utility_Dependency_Element + */ + public function addElement($table, $id, array $data = array()) { + $element = $this->getFactory()->getElement($table, $id, $data, $this); + $elementName = $element->__toString(); + $this->elements[$elementName] = $element; + return $element; + } + + /** + * Gets the outermost parents that define complete dependent structure each. + * + * @return array + */ + public function getOuterMostParents() { + if (!isset($this->outerMostParents)) { + $this->outerMostParents = array(); + + /** @var $element t3lib_utility_Dependency_Element */ + foreach ($this->elements as $element) { + $this->processOuterMostParent($element); + } + } + + return $this->outerMostParents; + } + + /** + * Processes and registers the outermost parents accordant to the registered elements. + * + * @param t3lib_utility_Dependency_Element $element + * @return void + */ + protected function processOuterMostParent(t3lib_utility_Dependency_Element $element) { + if ($this->outerMostParentsRequireReferences === FALSE || $element->hasReferences()) { + $outerMostParent = $element->getOuterMostParent(); + + if ($outerMostParent !== FALSE) { + $outerMostParentName = $outerMostParent->__toString(); + if (!isset($this->outerMostParents[$outerMostParentName])) { + $this->outerMostParents[$outerMostParentName] = $outerMostParent; + } + } + } + } + + /** + * Gets all nested elements (including the parent) of a particular outermost parent element. + * + * @throws RuntimeException + * @param t3lib_utility_Dependency_Element $outerMostParent + * @return array + */ + public function getNestedElements(t3lib_utility_Dependency_Element $outerMostParent) { + $outerMostParentName = $outerMostParent->__toString(); + + if (!isset($this->outerMostParents[$outerMostParentName])) { + throw new RuntimeException( + 'Element "' . $outerMostParentName . '" was detected as outermost parent.', + 1289318609 + ); + } + + $nestedStructure = array_merge( + array($outerMostParentName => $outerMostParent), + $outerMostParent->getNestedChildren() + ); + + return $nestedStructure; + } + + /** + * Gets the registered elements. + * + * @return array + */ + public function getElements() { + return $this->elements; + } + + /** + * Gets an instance of the factory to keep track of element or reference entities. + * + * @return t3lib_utility_Dependency_Factory + */ + public function getFactory() { + if (!isset($this->factory)) { + $this->factory = t3lib_div::makeInstance('t3lib_utility_Dependency_Factory'); + } + return $this->factory; + } +} Index: t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php =================================================================== --- t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php (Revision 0) +++ t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php (Revision 40148) @@ -0,0 +1,98 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Object to create and keep track of element or reference entities. + */ +class t3lib_utility_Dependency_Factory { + /** + * @var array + */ + protected $elements = array(); + + /** + * @var array + */ + protected $references = array(); + + /** + * Gets and registers a new element. + * + * @param string $table + * @param integer $id + * @param array $data (optional) + * @param t3lib_utility_Dependency $dependency + * @return t3lib_utility_Dependency_Element + */ + public function getElement($table, $id, array $data = array(), t3lib_utility_Dependency $dependency) { + $elementName = $table . ':' . $id; + if (!isset($this->elements[$elementName])) { + $this->elements[$elementName] = t3lib_div::makeInstance( + 't3lib_utility_Dependency_Element', + $table, $id, $data, $dependency + ); + } + return $this->elements[$elementName]; + } + + /** + * Gets and registers a new reference. + * + * @param t3lib_utility_Dependency_Element $element + * @param string $field + * @return t3lib_utility_Dependency_Reference + */ + public function getReference(t3lib_utility_Dependency_Element $element, $field) { + $referenceName = $element->__toString() . '.' . $field; + if (!isset($this->references[$referenceName][$field])) { + $this->references[$referenceName][$field] = t3lib_div::makeInstance( + 't3lib_utility_Dependency_Reference', + $element, $field + ); + } + return $this->references[$referenceName][$field]; + } + + /** + * Gets and registers a new reference. + * + * @param string $table + * @param integer $id + * @param string $field + * @param array $data (optional + * @param t3lib_utility_Dependency $dependency + * @return t3lib_utility_Dependency_Reference + * @see getElement + * @see getReference + */ + public function getReferencedElement($table, $id, $field, array $data = array(), t3lib_utility_Dependency $dependency) { + return $this->getReference( + $this->getElement($table, $id, $data, $dependency), + $field + ); + } +} Index: t3lib/utility/dependency/class.t3lib_utility_dependency_element.php =================================================================== --- t3lib/utility/dependency/class.t3lib_utility_dependency_element.php (Revision 0) +++ t3lib/utility/dependency/class.t3lib_utility_dependency_element.php (Revision 40148) @@ -0,0 +1,316 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Object to hold information on a dependent database element in abstract. + */ +class t3lib_utility_Dependency_Element { + const REFERENCES_ChildOf = 'childOf'; + const REFERENCES_ParentOf = 'parentOf'; + const EVENT_Construct = 't3lib_utility_Dependency_Element::construct'; + const EVENT_CreateChildReference = 't3lib_utility_Dependency_Element::createChildReference'; + const EVENT_CreateParentReference = 't3lib_utility_Dependency_Element::createParentReference'; + const RESPONSE_Skip = 't3lib_utility_Dependency_Element->skip'; + + /** + * @var string + */ + protected $table; + + /** + * @var integer + */ + protected $id; + + /** + * @var array + */ + protected $data; + + /** + * @var t3lib_utility_Dependency + */ + protected $dependency; + + /** + * @var array + */ + protected $children; + + /** + * @var array + */ + protected $parents; + + /** + * @var boolean + */ + protected $traversingParents = FALSE; + + /** + * @var t3lib_utility_Dependency_Element + */ + protected $outerMostParent; + + /** + * @var array + */ + protected $nestedChildren; + + /** + * Creates this object. + * + * @param string $table + * @param integer $id + * @param array $data (optional) + * @param t3lib_utility_Dependency $dependency + */ + public function __construct($table, $id, array $data = array(), t3lib_utility_Dependency $dependency) { + $this->table = $table; + $this->id = intval($id); + $this->data = $data; + $this->dependency = $dependency; + + $this->dependency->executeEventCallback(self::EVENT_Construct, $this); + } + + /** + * Gets the table. + * + * @return string + */ + public function getTable() { + return $this->table; + } + + /** + * Gets the id. + * + * @return integer + */ + public function getId() { + return $this->id; + } + + /** + * Gets the data. + * + * @return array + */ + public function getData() { + return $this->data; + } + + /** + * Gets a value for a particular key from the data. + * + * @param string $key + * @return mixed + */ + public function getDataValue($key) { + $result = NULL; + + if ($this->hasDataValue($key)) { + $result = $this->data[$key]; + } + + return $result; + } + + /** + * Sets a value for a particular key in the data. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function setDataValue($key, $value) { + $this->data[$key] = $value; + } + + /** + * Determines whether a particular key holds data. + * + * @param string $key + * @return + */ + public function hasDataValue($key) { + return (isset($this->data[$key])); + } + + /** + * Converts this object for string representation. + * + * @return string + */ + public function __toString() { + return self::getIdentifier($this->table, $this->id); + } + + /** + * Gets the parent dependency object. + * + * @return t3lib_utility_Dependency + */ + public function getDependency() { + return $this->dependency; + } + + /** + * Gets all child references. + * + * @return array + */ + public function getChildren() { + if (!isset($this->children)) { + $this->children = array(); + $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows( + '*', + 'sys_refindex', + 'tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->table, 'sys_refindex') . + ' AND recuid=' . $this->id + ); + if (is_array($rows)) { + foreach ($rows as $row) { + $reference = $this->getDependency()->getFactory()->getReferencedElement( + $row['ref_table'], $row['ref_uid'], $row['field'], array(), $this->getDependency() + ); + $callbackResponse = $this->dependency->executeEventCallback( + self::EVENT_CreateChildReference, + $this, array('reference' => $reference) + ); + if ($callbackResponse !== self::RESPONSE_Skip) { + $this->children[] = $reference; + } + } + } + } + return $this->children; + } + + /** + * Gets all parent references. + * + * @return array + */ + public function getParents() { + if (!isset($this->parents)) { + $this->parents = array(); + $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows( + '*', + 'sys_refindex', + 'ref_table=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->table, 'sys_refindex') . + ' AND deleted=0 AND ref_uid=' . $this->id + ); + if (is_array($rows)) { + foreach ($rows as $row) { + $reference = $this->getDependency()->getFactory()->getReferencedElement( + $row['tablename'], $row['recuid'], $row['field'], array(), $this->getDependency() + ); + $callbackResponse = $this->dependency->executeEventCallback( + self::EVENT_CreateParentReference, + $this, array('reference' => $reference) + ); + if ($callbackResponse !== self::RESPONSE_Skip) { + $this->parents[] = $reference; + } + } + } + } + return $this->parents; + } + + /** + * Determines whether there are child or parent references. + * + * @return boolean + */ + public function hasReferences() { + return (count($this->getChildren()) > 0 || count($this->getParents()) > 0); + } + + /** + * Gets the outermost parent element. + * + * @return t3lib_utility_Dependency_Element + */ + public function getOuterMostParent() { + if (!isset($this->outerMostParent)) { + $parents = $this->getParents(); + if (count($parents) === 0) { + $this->outerMostParent = $this; + } else { + $this->outerMostParent = FALSE; + /** @var $parent t3lib_utility_Dependency_Reference */ + foreach ($parents as $parent) { + $outerMostParent = $parent->getElement()->getOuterMostParent(); + if ($outerMostParent instanceof t3lib_utility_Dependency_Element) { + $this->outerMostParent = $outerMostParent; + break; + } elseif ($outerMostParent === FALSE) { + break; + } + } + } + } + + return $this->outerMostParent; + } + + /** + * Gets nested children accumulated. + * + * @return array + */ + public function getNestedChildren() { + if (!isset($this->nestedChildren)) { + $this->nestedChildren = array(); + $children = $this->getChildren(); + /** @var $child t3lib_utility_Dependency_Reference */ + foreach ($children as $child) { + $this->nestedChildren = array_merge( + $this->nestedChildren, + array($child->getElement()->__toString() => $child->getElement()), + $child->getElement()->getNestedChildren() + ); + } + } + + return $this->nestedChildren; + } + + /** + * Converts the object for string representation. + * + * @param string $table + * @param integer $id + * @return string + */ + public static function getIdentifier($table, $id) { + return $table . ':' . $id; + } +} \ No newline at end of file Index: t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php =================================================================== --- t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php (Revision 0) +++ t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php (Revision 40148) @@ -0,0 +1,75 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Object to hold information on a callback to a defined object and method. + */ +class t3lib_utility_Dependency_Callback { + /** + * @var object + */ + protected $object; + + /** + * @var string + */ + protected $method; + + /** + * @var array + */ + protected $targetArguments; + + /** + * Creates the objects. + * + * @param object $object + * @param string $method + * @param array $targetArguments (optional) + */ + public function __construct($object, $method, array $targetArguments = array()) { + $this->object = $object; + $this->method = $method; + $this->targetArguments = $targetArguments; + $this->targetArguments['target'] = $object; + } + + /** + * Executes the callback. + * + * @param array $callerArguments + * @param object $caller + * @param string $eventName + * @return mixed + */ + public function execute(array $callerArguments = array(), $caller, $eventName) { + return call_user_func_array( + array($this->object, $this->method), + array($callerArguments, $this->targetArguments, $caller, $eventName) + ); + } +} Index: t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php =================================================================== --- t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php (Revision 0) +++ t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php (Revision 40148) @@ -0,0 +1,79 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * A copy is found in the textfile GPL.txt and important notices to the license + * from the author is found in LICENSE.txt distributed with these scripts. + * + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Object to hold reference information of a database field and one accordant element. + */ +class t3lib_utility_Dependency_Reference { + /** + * @var t3lib_utility_Dependency_Element + */ + protected $element; + + /** + * @var string + */ + protected $field; + + /** + * Creates this object. + * + * @param t3lib_utility_Dependency_Element $element + * @param string $field + */ + public function __construct(t3lib_utility_Dependency_Element $element, $field) { + $this->element = $element; + $this->field = $field; + } + + /** + * Gets the elements. + * + * @return t3lib_utility_Dependency_Element + */ + public function getElement() { + return $this->element; + } + + /** + * Gets the field. + * + * @return string + */ + public function getField() { + return $this->field; + } + + /** + * Converts this object for string representation. + * + * @return string + */ + public function __toString() { + return $this->element . '.' . $this->field; + } +} \ No newline at end of file Index: t3lib/class.t3lib_befunc.php =================================================================== --- t3lib/class.t3lib_befunc.php (Revision 38989) +++ t3lib/class.t3lib_befunc.php (Arbeitskopie) @@ -3924,16 +3924,31 @@ * @return array If found, the record, otherwise nothing. */ public static function getLiveVersionOfRecord($table, $uid, $fields = '*') { - global $TCA; + $liveVersionId = self::getLiveVersionIdOfRecord($table, $uid); - // Check that table supports versioning: - if ($TCA[$table] && $TCA[$table]['ctrl']['versioningWS']) { - $rec = self::getRecord($table, $uid, 'pid,t3ver_oid'); + if (is_null($liveVersionId) === FALSE) { + return self::getRecord($table, $liveVersionId, $fields); + } + } - if ($rec['pid']==-1) { - return self::getRecord($table, $rec['t3ver_oid'], $fields); + /** + * Gets the id of the live version of a record. + * + * @param string $table Name of the table + * @param integer $uid Uid of the offline/draft record + * @return integer The id of the live version of the record (or NULL if nothing was found) + */ + public static function getLiveVersionIdOfRecord($table, $uid) { + $liveVersionId = NULL; + + if (self::isTableWorkspaceEnabled($table)) { + $currentRecord = self::getRecord($table, $uid, 'pid,t3ver_oid'); + if (is_array($currentRecord) && $currentRecord['pid'] == -1) { + $liveVersionId = $currentRecord['t3ver_oid']; } } + + return $liveVersionId; } /** @@ -3975,6 +3990,29 @@ } /** + * Get additional where clause to select records of a specific workspace (includes live as well). + * + * @param $table + * @param $workspaceId + * @return string + */ + public static function getWorkspaceWhereClause($table, $workspaceId = NULL) { + $whereClause = ''; + + if (is_null($workspaceId)) { + $workspaceId = $GLOBALS['BE_USER']->workspace; + } + + if (self::isTableWorkspaceEnabled($table)) { + $workspaceId = intval($workspaceId); + $pidOperator = ($workspaceId === 0 ? '!=' : '='); + $whereClause = ' AND ' . $table . '.t3ver_wsid=' . $workspaceId . ' AND ' . $table . '.pid' . $pidOperator . '-1'; + } + + return $whereClause; + } + + /** * Count number of versions on a page * * @param integer Workspace ID @@ -4447,5 +4485,33 @@ return $script; } + + /** + * Determines whether a table is enabled for workspaces. + * + * @param $table Name of the table to be checked + * @return boolean + */ + public static function isTableWorkspaceEnabled($table) { + return (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']); + } + + /** + * Gets the TCA configuration of a field. + * + * @param string $table Name of the table + * @param string $field Name of the field + * @return array + */ + public static function getTcaFieldConfiguration($table, $field) { + $configuration = array(); + t3lib_div::loadTCA($table); + + if (isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) { + $configuration = $GLOBALS['TCA'][$table]['columns'][$field]['config']; + } + + return $configuration; + } } ?> \ No newline at end of file Index: t3lib/core_autoload.php =================================================================== --- t3lib/core_autoload.php (Revision 38989) +++ t3lib/core_autoload.php (Arbeitskopie) @@ -67,6 +67,7 @@ 't3lib_tceforms_fe' => PATH_t3lib . 'class.t3lib_tceforms_fe.php', 't3lib_tceforms_inline' => PATH_t3lib . 'class.t3lib_tceforms_inline.php', 't3lib_tcemain' => PATH_t3lib . 'class.t3lib_tcemain.php', + 't3lib_tcemain_commandmap' => PATH_t3lib . 'class.t3lib_tcemain_commandmap.php', 't3lib_timetrack' => PATH_t3lib . 'class.t3lib_timetrack.php', 't3lib_timetracknull' => PATH_t3lib . 'class.t3lib_timetracknull.php', 't3lib_transferdata' => PATH_t3lib . 'class.t3lib_transferdata.php', @@ -128,6 +129,11 @@ 't3lib_tceforms_suggest' => PATH_t3lib . 'tceforms/class.t3lib_tceforms_suggest.php', 't3lib_tceforms_suggest_defaultreceiver' => PATH_t3lib . 'tceforms/class.t3lib_tceforms_suggest_defaultreceiver.php', 't3lib_utility_client' => PATH_t3lib . 'utility/class.t3lib_utility_client.php', + 't3lib_utility_dependency' => PATH_t3lib . 'utility/class.t3lib_utility_dependency.php', + 't3lib_utility_dependency_callback' => PATH_t3lib . 'utility/dependency/class.t3lib_utility_dependency_callback.php', + 't3lib_utility_dependency_element' => PATH_t3lib . 'utility/dependency/class.t3lib_utility_dependency_element.php', + 't3lib_utility_dependency_factory' => PATH_t3lib . 'utility/dependency/class.t3lib_utility_dependency_factory.php', + 't3lib_utility_dependency_reference' => PATH_t3lib . 'utility/dependency/class.t3lib_utility_dependency_reference.php', 't3lib_utility_http' => PATH_t3lib . 'utility/class.t3lib_utility_http.php', 't3lib_utility_mail' => PATH_t3lib . 'utility/class.t3lib_utility_mail.php', 't3lib_spritemanager' => PATH_t3lib . 'class.t3lib_spritemanager.php', Index: t3lib/class.t3lib_tceforms_inline.php =================================================================== --- t3lib/class.t3lib_tceforms_inline.php (Revision 38989) +++ t3lib/class.t3lib_tceforms_inline.php (Arbeitskopie) @@ -2,7 +2,7 @@ /*************************************************************** * Copyright notice * -* (c) 2006-2010 Oliver Hader +* (c) 2006-2010 Oliver Hader * All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is @@ -29,7 +29,7 @@ * * $Id: class.t3lib_tceforms_inline.php 8996 2010-10-06 21:15:07Z psychomieze $ * - * @author Oliver Hader + * @author Oliver Hader */ /** * [CLASS/FUNCTION INDEX of SCRIPT] @@ -205,7 +205,8 @@ } // If the parent is a page, use the uid(!) of the (new?) page as pid for the child records: if ($table == 'pages') { - $this->inlineFirstPid = $row['uid']; + $liveVersionId = t3lib_BEfunc::getLiveVersionIdOfRecord('pages', $row['uid']); + $this->inlineFirstPid = (is_null($liveVersionId) ? $row['uid'] : $liveVersionId); // If pid is negative, fetch the previous record and take its pid: } elseif ($row['pid'] < 0) { $prevRec = t3lib_BEfunc::getRecord($table, abs($row['pid'])); @@ -1699,6 +1700,14 @@ * @return array A record row from the database post-processed by t3lib_transferData */ function getRecord($pid, $table, $uid, $cmd='') { + // Fetch workspace version of a record (if any): + if ($cmd !== 'new' && $GLOBALS['BE_USER']->workspace !== 0) { + $workspaceVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $table, $uid, 'uid'); + if ($workspaceVersion !== FALSE) { + $uid = $workspaceVersion['uid']; + } + } + $trData = t3lib_div::makeInstance('t3lib_transferData'); $trData->addRawData = TRUE; $trData->lockRecords=1; Index: typo3/mod/user/ws/index.php =================================================================== --- typo3/mod/user/ws/index.php (Revision 38989) +++ typo3/mod/user/ws/index.php (Arbeitskopie) @@ -240,6 +240,7 @@ $tce->stripslashes_values = 0; $tce->start(array(), $cmdArray); $tce->process_cmdmap(); + $tce->printLogErrorMessages(''); } } }