Add feature #16374: Implement Inline Relational Record Editing (IRRE) in Workspaces
authorOliver Hader <oliver.hader@typo3.org>
Mon, 15 Nov 2010 13:49:42 +0000 (13:49 +0000)
committerOliver Hader <oliver.hader@typo3.org>
Mon, 15 Nov 2010 13:49:42 +0000 (13:49 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@9385 709f56b5-9817-0410-a4d7-c38de5d9e867

16 files changed:
ChangeLog
t3lib/class.t3lib_befunc.php
t3lib/class.t3lib_loaddbgroup.php
t3lib/class.t3lib_tceforms_inline.php
t3lib/class.t3lib_tcemain.php
t3lib/core_autoload.php
t3lib/utility/class.t3lib_utility_dependency.php [new file with mode: 0644]
t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php [new file with mode: 0644]
t3lib/utility/dependency/class.t3lib_utility_dependency_element.php [new file with mode: 0644]
t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php [new file with mode: 0644]
t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php [new file with mode: 0644]
typo3/sysext/version/class.t3lib_tcemain_commandmap.php [new file with mode: 0644]
typo3/sysext/version/class.tx_version_tcemain.php
typo3/sysext/version/ext_autoload.php
typo3/sysext/version/ext_localconf.php
typo3/sysext/version/ws/index.php

index 6273865..7fc6cc2 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2010-11-15  Oliver Hader  <oliver@typo3.org>
+
+       * Add feature #16374: Implement Inline Relational Record Editing (IRRE) in Workspaces
+
 2010-11-15  Ernesto Baschny  <ernst@cron-it.de>
 
        * Added feature #16170: Increase DB-fields spaceBefore and spaceAfter (tt_content) (Thanks to Henrik Ziegenhain)
index 0c12b2b..c481609 100644 (file)
@@ -4004,16 +4004,31 @@ final class t3lib_BEfunc {
         * @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;
        }
 
        /**
@@ -4064,6 +4079,29 @@ final class t3lib_BEfunc {
        }
 
        /**
+        * 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
@@ -4510,5 +4548,33 @@ final class t3lib_BEfunc {
 
                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 8c468c6..7c7cfde 100644 (file)
@@ -101,6 +101,10 @@ class t3lib_loadDBGroup    {
        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 @@ class t3lib_loadDBGroup    {
        }
 
        /**
+        * 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 @@ class t3lib_loadDBGroup    {
                        $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 @@ class t3lib_loadDBGroup   {
         *
         * @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 1f713de..1a4e436 100644 (file)
@@ -2,7 +2,7 @@
 /***************************************************************
 *  Copyright notice
 *
-*  (c) 2006-2010 Oliver Hader <oh@inpublica.de>
+*  (c) 2006-2010 Oliver Hader <oliver@typo3.org>
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -29,7 +29,7 @@
  *
  * $Id$
  *
- * @author     Oliver Hader <oh@inpublica.de>
+ * @author     Oliver Hader <oliver@typo3.org>
  */
 /**
  * [CLASS/FUNCTION INDEX of SCRIPT]
@@ -205,7 +205,8 @@ class t3lib_TCEforms_inline {
                        }
                                // 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']));
@@ -1701,6 +1702,14 @@ class t3lib_TCEforms_inline {
         * @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 01c17e1..44d8cf3 100644 (file)
@@ -337,6 +337,9 @@ class t3lib_TCEmain {
        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[<table>][<uid>] = <index in $remapStack>)
+       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
@@ -911,7 +914,13 @@ class t3lib_TCEmain        {
                                                                                        }
                                                                                        $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;
                                                                                        }
@@ -1533,6 +1542,7 @@ class t3lib_TCEmain       {
                                // 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),
@@ -1957,6 +1967,7 @@ class t3lib_TCEmain       {
                        // 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),
@@ -2664,6 +2675,7 @@ class t3lib_TCEmain       {
                        // Finally, before exit, check if there are ID references to remap.
                        // This might be the case if versioning or copying has taken place!
                $this->remapListedDBRecords();
+               $this->processRemapStack();
 
                foreach ($hookObjectsArr as $hookObj) {
                        if (method_exists($hookObj, 'processCmdmap_afterFinish')) {
@@ -2809,6 +2821,10 @@ class t3lib_TCEmain      {
                                                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
@@ -3115,8 +3131,17 @@ class t3lib_TCEmain      {
                                        } 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);
                                                }
@@ -3761,6 +3786,16 @@ class t3lib_TCEmain      {
 
                                                                                                // 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:
@@ -3855,10 +3890,12 @@ class t3lib_TCEmain     {
                                                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;
                                                        }
                                                }
@@ -4794,9 +4831,15 @@ class t3lib_TCEmain      {
                                $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;
@@ -4806,16 +4849,11 @@ class t3lib_TCEmain     {
                                        $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);
                                                }
                                        }
@@ -4831,6 +4869,7 @@ class t3lib_TCEmain       {
         * @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:
@@ -4900,9 +4939,72 @@ class t3lib_TCEmain      {
                                }
                        }
                }
+                       // 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
+        */
+       public function addRemapStackRefIndex($table, $id) {
+               $this->remapStackRefIndex[$table][$id] = $id;
        }
 
        /**
@@ -6920,6 +7022,54 @@ class t3lib_TCEmain      {
                }
                return $result;
        }
+
+       /**
+        * 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;
+                       }
+               }
+       }
 }
 
 
index 142086c..e27ab58 100644 (file)
@@ -135,6 +135,11 @@ $t3libClasses = array(
        '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_utility_phpoptions' => PATH_t3lib . 'utility/class.t3lib_utility_phpoptions.php',
diff --git a/t3lib/utility/class.t3lib_utility_dependency.php b/t3lib/utility/class.t3lib_utility_dependency.php
new file mode 100644 (file)
index 0000000..e021fcd
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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;
+       }
+}
diff --git a/t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php b/t3lib/utility/dependency/class.t3lib_utility_dependency_callback.php
new file mode 100644 (file)
index 0000000..04b272c
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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)
+               );
+       }
+}
diff --git a/t3lib/utility/dependency/class.t3lib_utility_dependency_element.php b/t3lib/utility/dependency/class.t3lib_utility_dependency_element.php
new file mode 100644 (file)
index 0000000..af6978d
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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
diff --git a/t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php b/t3lib/utility/dependency/class.t3lib_utility_dependency_factory.php
new file mode 100644 (file)
index 0000000..23c246b
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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
+               );
+       }
+}
diff --git a/t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php b/t3lib/utility/dependency/class.t3lib_utility_dependency_reference.php
new file mode 100644 (file)
index 0000000..2b876c4
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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
diff --git a/typo3/sysext/version/class.t3lib_tcemain_commandmap.php b/typo3/sysext/version/class.t3lib_tcemain_commandmap.php
new file mode 100644 (file)
index 0000000..f972a05
--- /dev/null
@@ -0,0 +1,771 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Oliver Hader <oliver@typo3.org>
+ * 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 tx_version_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 tx_version_tcemain
+        */
+       protected $parent;
+
+       /**
+        * @var t3lib_TCEmain
+        */
+       protected $tceMain;
+
+       /**
+        * @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(tx_version_tcemain $parent, t3lib_TCEmain $tceMain, array $commandMap) {
+               $this->setParent($parent);
+               $this->setTceMain($tceMain);
+               $this->set($commandMap);
+
+               $this->setWorkspacesSwapMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.swapMode'));
+               $this->setWorkspacesChangeStageMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.changeStageMode'));
+               $this->setWorkspacesConsiderReferences($this->getTceMain()->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 tx_version_tcemain_CommandMap
+        */
+       public function set(array $commandMap) {
+               $this->commandMap = $commandMap;
+               return $this;
+       }
+
+       /**
+        * Gets the parent object.
+        *
+        * @return tx_version_tcemain
+        */
+       public function getParent() {
+               return $this->parent;
+       }
+
+       /**
+        * Sets the parent object.
+        *
+        * @param tx_version_tcemain $parent
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setParent(tx_version_tcemain $parent) {
+               $this->parent = $parent;
+               return $this;
+       }
+
+       /**
+        * Gets the parent object.
+        *
+        * @return t3lib_TCEmain
+        */
+       public function getTceMain() {
+               return $this->tceMain;
+       }
+
+       /**
+        * Sets the parent object.
+        *
+        * @param t3lib_TCEmain $parent
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setTceMain(t3lib_TCEmain $tceMain) {
+               $this->tceMain = $tceMain;
+               return $this;
+       }
+
+       /**
+        * Sets the workspaces swap mode
+        * (see options.workspaces.swapMode).
+        *
+        * @param string $workspacesSwapMode
+        * @return tx_version_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 tx_version_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 tx_version_tcemain_CommandMap
+        */
+       public function setWorkspacesConsiderReferences($workspacesConsiderReferences) {
+               $this->workspacesConsiderReferences = (bool)$workspacesConsiderReferences;
+               return $this;
+       }
+
+       /**
+        * Processes the command map.
+        *
+        * @return tx_version_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->tceMain()->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->getTceMain()->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->getTceMain()->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->getTceMain()->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 0a9d4da..fb6a95f 100644 (file)
@@ -48,14 +48,16 @@ class tx_version_tcemain {
         ****************************/
 
        /**
-        * hook that is called before any cmd of the commandmap is 
-        * executed
-        * @param       $tcemainObj     reference to the main tcemain object
-        * @return      void
+        * hook that is called before any cmd of the commandmap is executed
+        *
+        * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
+        * @return void
         */
-       public function processCmdmap_beforeStart(&$tcemainObj) {
+       public function processCmdmap_beforeStart(t3lib_TCEmain $tcemainObj) {
                        // Reset notification array
                $this->notificationEmailInfo = array();
+                       // Resolve dependencies of version/workspaces actions:
+               $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj, $tcemainObj->cmdmap)->process()->get();
        }
 
 
@@ -94,23 +96,7 @@ class tx_version_tcemain {
                                break;
 
                                case 'swap':
-                                       $swapMode = $tcemainObj->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($tcemainObj->cmdmap) == 1 && count($tcemainObj->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'], $tcemainObj);
-                                               }
-                                       }
+                                       $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'], $tcemainObj);
                                break;
 
                                case 'clearWSID':
@@ -122,35 +108,13 @@ class tx_version_tcemain {
                                break;
 
                                case 'setStage':
-                                       $elementList = array();
-                                       $idList = $elementList[$table] = t3lib_div::trimExplode(',', $id, 1);
-                                       $setStageMode = $tcemainObj->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 = $tcemainObj->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);
-                                               }
-                                       }
-
-                                       foreach ($elementList as $tbl => $elementIdList) {
-                                               foreach ($elementIdList as $elementId) {
-                                                       $this->version_setStage($tbl, $elementId, $value['stageId'], ($value['comment'] ? $value['comment'] : $this->generalComment), TRUE, $tcemainObj);
-                                               }
+                                       $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,
+                                                       $tcemainObj
+                                               );
                                        }
                                break;
                        }
@@ -463,7 +427,7 @@ class tx_version_tcemain {
                                if ($emailMessage && $emailSubject) {
                                        t3lib_div::deprecationLog('This TYPO3 installation uses Workspaces staging notification by setting the TSconfig options "TCEMAIN.notificationEmail_subject" / "TCEMAIN.notificationEmail_body". Please use the more flexible marker-based options tx_version.workspaces.stageNotificationEmail.message / tx_version.workspaces.stageNotificationEmail.subject');
 
-                                       $emailSubject = sprintf($subject, $elementName);
+                                       $emailSubject = sprintf($emailSubject, $elementName);
                                        $emailMessage = sprintf($emailMessage,
                                                $markers['###SITE_NAME###'],
                                                $markers['###SITE_URL###'],
@@ -521,6 +485,7 @@ class tx_version_tcemain {
                                                        if (!isset($languageObjects[$recipientLanguage])) {
                                                                        // a LANG object in this language hasn't been 
                                                                        // instantiated yet, so this is done here
+                                                               /** @var $languageObject language */
                                                                $languageObject = t3lib_div::makeInstance('language');
                                                                $languageObject->init($recipientLanguage);
                                                                $languageObjects[$recipientLanguage] = $languageObject;
@@ -662,6 +627,9 @@ class tx_version_tcemain {
                                }
                        }
 
+                               // 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 ($tcemainObj->BE_USER->workspaceVersioningTypeAccess($versionizeTree)) {
 
@@ -866,7 +834,7 @@ class tx_version_tcemain {
                                                                                                } else {
                                                                                                        // Otherwise update the movePlaceholder:
                                                                                                        $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh);
-                                                                                                       $tcemainObj->updateRefIndex($table, $movePlhID);
+                                                                                                       $tcemainObj->addRemapStackRefIndex($table, $movePlhID);
                                                                                                }
                                                                                        }
 
@@ -880,7 +848,7 @@ class tx_version_tcemain {
                                                                                        $tcemainObj->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
 
                                                                                                // Update reference index of the live record:
-                                                                                       $tcemainObj->updateRefIndex($table, $id);
+                                                                                       $tcemainObj->addRemapStackRefIndex($table, $id);
 
                                                                                                // Set log entry for live record:
                                                                                        $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion);
@@ -893,7 +861,7 @@ class tx_version_tcemain {
                                                                                        $tcemainObj->setHistory($table, $id, $theLogId);
 
                                                                                                // Update reference index of the offline record:
-                                                                                       $tcemainObj->updateRefIndex($table, $swapWith);
+                                                                                       $tcemainObj->addRemapStackRefIndex($table, $swapWith);
                                                                                                // Set log entry for offline record:
                                                                                        $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion);
                                                                                        if ($propArr['_ORIG_pid'] == -1) {
@@ -970,16 +938,25 @@ class tx_version_tcemain {
 
                        // 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) {
+                               $tcemainObj->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') {
@@ -1066,7 +1043,7 @@ class tx_version_tcemain {
                                        );
                                        while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
                                                        // Check, if this record has already been copied by a parent record as relation:
-                                               if (!$this->copyMappingArray[$table][$row['uid']]) {
+                                               if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) {
                                                                // Copying each of the underlying records (method RAW)
                                                        $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId);
                                                }
@@ -1303,6 +1280,43 @@ class tx_version_tcemain {
                $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid);
        }
 
+       /**
+        * 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 an instance of the command map helper.
+        *
+        * @param t3lib_TCEmain $tceMain
+        * @param  $commandMap
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function getCommandMap(t3lib_TCEmain $tceMain, array $commandMap) {
+               return t3lib_div::makeInstance('tx_version_tcemain_CommandMap', $this, $tceMain, $commandMap);
+       }
 }
 
 ?>
\ No newline at end of file
index fa789cf..78a9e62 100644 (file)
@@ -1,11 +1,13 @@
 <?php
-/*
- * Register necessary classes with autoloader
- *
- * $Id: ext_autoload.php 6536 2009-11-25 14:07:18Z stucki $
- */
+// DO NOT CHANGE THIS FILE! It is automatically generated by extdeveval::buildAutoloadRegistry.
+// This file was generated on 2010-11-13 17:45
+
+$extensionPath = t3lib_extMgm::extPath('version');
 return array(
-       'tx_version_tasks_autopublish' => t3lib_extMgm::extPath('version', 'tasks/class.tx_version_tasks_autopublish.php'),
-       'tx_version_gui' => t3lib_extMgm::extPath('version', 'class.tx_version_gui.php')
+       'tx_version_tcemain_commandmap' => $extensionPath . 'class.t3lib_tcemain_commandmap.php',
+       'tx_version_cm1' => $extensionPath . 'cm1/index.php',
+       'tx_version_gui' => $extensionPath . 'class.tx_version_gui.php',
+       'tx_version_tcemain' => $extensionPath . 'class.tx_version_tcemain.php',
+       'tx_version_tasks_autopublish' => $extensionPath . 'tasks/class.tx_version_tasks_autopublish.php',
 );
-?>
+?>
\ No newline at end of file
index f8ecfa3..f23452c 100644 (file)
@@ -6,8 +6,8 @@ if (!defined ('TYPO3_MODE')) {
 }
 
        // register the hook to actually do the work within TCEmain
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = t3lib_extMgm::extPath('version', 'class.tx_version_tcemain.php:&tx_version_tcemain');
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'][] = t3lib_extMgm::extPath('version', 'class.tx_version_tcemain.php:&tx_version_tcemain');
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['version'] = t3lib_extMgm::extPath('version', 'class.tx_version_tcemain.php:&tx_version_tcemain');
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['version'] = t3lib_extMgm::extPath('version', 'class.tx_version_tcemain.php:&tx_version_tcemain');
 
 if (TYPO3_MODE == 'BE') {
 
index ec6068b..b4ddaed 100755 (executable)
@@ -236,12 +236,12 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                                        }
                                }
 
-               #               debug($cmdArray);
-
+                               /** @var $tce t3lib_TCEmain */
                                $tce = t3lib_div::makeInstance('t3lib_TCEmain');
                                $tce->stripslashes_values = 0;
                                $tce->start(array(), $cmdArray);
                                $tce->process_cmdmap();
+                               $tce->printLogErrorMessages('');
                        }
                }
        }