TYPO3 API  SVNRelease
class.tx_coreupdates_migrateworkspaces.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2010-2011 Tolleiv Nietsch <info@tolleiv.de>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 
00029 /**
00030  * Migrates workspaces from TYPO3 versions below 4.5.
00031  *
00032  * @author Tolleiv Nietsch <info@tolleiv.de>
00033  * @version $Id$
00034  */
00035 class tx_coreupdates_migrateworkspaces extends tx_coreupdates_installsysexts {
00036     protected $title = 'Versioning and Workspaces';
00037 
00038     public $sqlQueries;
00039 
00040     /**
00041      * Checks if an update is needed
00042      *
00043      * @param   string      &$description: The description for the update, which will be updated with a description of the script's purpose
00044      * @return  boolean     whether an update is needed (true) or not (false)
00045      */
00046     public function checkForUpdate(&$description) {
00047         $result = FALSE;
00048         $description = 'Migrates the old hardcoded draft workspace to be a real workspace record,
00049         updates workspace owner fields to support either users or groups and
00050         migrates the old-style workspaces with fixed workflow to a custom-stage workflow. If required
00051         the extbase, fluid, version and workspaces extensions are installed.';
00052 
00053         $reason = '';
00054             // TYPO3 version 4.5 and above
00055         if ($this->versionNumber >= 4005000) {
00056                 // If neither version nor workspaces is installed, we're not doing a migration
00057                 // Present the user with the choice of activating versioning and workspaces
00058             if (!t3lib_extMgm::isLoaded('version') && !t3lib_extMgm::isLoaded('workspaces')) {
00059                 $result = TRUE;
00060                     // Override the default description
00061                 $description = 'Activates the usage of workspaces in your installation. Workspaces let you edit elements
00062                     without the changes being visible on the live web site right away. Modified elements can then go
00063                     through a validation process and eventually be published.<br /><br />';
00064                 $description .= 'This wizard will install system extensions "version" and "workspaces" (and may
00065                     install "fluid" and "extbase" too, as they are used by the "workspaces" extension).';
00066             } else {
00067 
00068                 $this->includeTCA();
00069 
00070                 if (!t3lib_extMgm::isLoaded('version') || !t3lib_extMgm::isLoaded('workspaces')) {
00071                     $result = TRUE;
00072                     $reason .= ' Both extensions "version" and "workspaces" need to be
00073                         present to use the entire versioning and workflow featureset of TYPO3.';
00074                 }
00075 
00076                 $tables = array_keys($GLOBALS['TYPO3_DB']->admin_get_tables());
00077                     // sys_workspace table might not exists if version extension was never installed
00078                 if (!in_array('sys_workspace', $tables) || !in_array('sys_workspace_stage', $tables)) {
00079                     $result = TRUE;
00080                     $reason .= ' The database tables for the workspace functionality are missing.';
00081                 } elseif ($this->isOldStyleAdminFieldUsed() || $this->isOldStyleWorkspace()) {
00082                     $wsCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', '');
00083                     $result |= $wsCount > 0;
00084                     $reason .= ' The existing workspaces will be checked for compatibility with the new features.';
00085                 }
00086 
00087                 $draftWorkspaceTestResult = $this->isDraftWorkspaceUsed();
00088                 if ($draftWorkspaceTestResult) {
00089                     $reason .= ' The old style draft workspace is used.
00090                         Related records will be moved into a full featured workspace.';
00091                     $result = TRUE;
00092                 }
00093 
00094                 $description .= '<br /><strong>Why do you need this wizard?</strong><br />' . $reason;
00095             }
00096         }
00097 
00098         return $result;
00099     }
00100 
00101     /**
00102      * This method requests input from the user about the upgrade process, if needed
00103      *
00104      * @param string $inputPrefix
00105      * @return void
00106      */
00107     public function getUserInput($inputPrefix) {
00108         $content = '';
00109 
00110         if (!t3lib_extMgm::isLoaded('version') && !t3lib_extMgm::isLoaded('workspaces')) {
00111                 // We need feedback only if versioning is not activated at all
00112                 // In such a case we want to leave the user with the choice of not activating the stuff at all
00113             $content = '
00114                 <fieldset>
00115                     <ol>
00116             ';
00117 
00118             $content .= '
00119                 <li class="labelAfter">
00120                     <input type="checkbox" id="versioning" name="' . $inputPrefix . '[versioning]" value="1" checked="checked" />
00121                     <label for="versioning">Activate workspaces?</label>
00122                 </li>
00123             ';
00124 
00125             $content .= '
00126                     </ol>
00127                 </fieldset>
00128             ';
00129 
00130         } else {
00131                 // No feedback needed, just include the update flag as a hidden field
00132             $content = '<input type="hidden" id="versioning" name="' . $inputPrefix . '[versioning]" value="1" />';
00133         }
00134 
00135         return $content;
00136     }
00137 
00138     /**
00139      * Performs the database update. Changes existing workspaces to use the new custom workspaces
00140      *
00141      * @param   array       &$databaseQueries: queries done in this update
00142      * @param   mixed       &$customMessages: custom messages
00143      * @return  boolean     whether it worked (true) or not (false)
00144      */
00145     public function performUpdate(array &$databaseQueries, &$customMessages) {
00146         $result = TRUE;
00147 
00148             // TYPO3 version below 4.5
00149         if ($this->versionNumber < 4005000) {
00150             return FALSE;
00151         }
00152             // Wizard skipped by the user
00153         if (empty($this->pObj->INSTALL['update']['migrateWorkspaces']['versioning'])) {
00154             return TRUE;
00155         }
00156 
00157             // There's no TCA available yet
00158         $this->includeTCA();
00159 
00160             // install version and workspace extension (especially when updating from very old TYPO3 versions
00161         $this->installExtensions(array('extbase', 'fluid', 'version', 'workspaces'));
00162 
00163             // migrate all workspaces to support groups and be_users
00164         if ($this->isOldStyleAdminFieldUsed()) {
00165             $this->migrateAdminFieldToNewStyle();
00166         }
00167 
00168             // create a new dedicated "Draft" workspace and move all records to that new workspace
00169         if ($this->isDraftWorkspaceUsed()) {
00170             $draftWorkspaceId = $this->createWorkspace();
00171             if (is_integer($draftWorkspaceId)) {
00172                 $this->migrateDraftWorkspaceRecordsToWorkspace($draftWorkspaceId);
00173             }
00174         }
00175 
00176         $workspaces = $this->getWorkspacesWithoutStages();
00177         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00178         $label = 'Review';
00179         foreach($workspaces as $workspace) {
00180                 // Find all workspaces and add "review" stage record
00181                 // Add review users and groups to the new IRRE record
00182             $reviewStageId = $this->createReviewStageForWorkspace($workspace['uid'], $label, $workspace['reviewers']);
00183                 // Update all "review" state records in the database to point to the new state
00184             $this->migrateOldRecordsToStage($workspace['uid'], 1, $reviewStageId);
00185                 // Update all "ready to publish" records in the database to point to the new ready to publish state
00186             $this->migrateOldRecordsToStage($workspace['uid'], 10, -99);
00187         }
00188 
00189         if (is_array($this->sqlQueries) && is_array($databaseQueries)) {
00190             $databaseQueries = array_merge($databaseQueries, $this->sqlQueries);
00191         }
00192 
00193         return $result;
00194     }
00195 
00196     /**
00197      * Check if any table contains draft-workspace records
00198      *
00199      * @return bool
00200      */
00201     protected function isDraftWorkspaceUsed() {
00202         $foundDraftRecords = FALSE;
00203 
00204         $tables = array_keys($GLOBALS['TCA']);
00205         foreach ($tables as $table) {
00206             $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
00207             if ($versioningVer > 0) {
00208                 if ($this->hasElementsOnWorkspace($table, -1)) {
00209                     $foundDraftRecords = TRUE;
00210                     break;
00211                 }
00212             }
00213         }
00214 
00215         return $foundDraftRecords;
00216     }
00217 
00218     /**
00219      * Find workspaces which have no sys_workspace_state(s) but have records using states
00220      * If "
00221      *
00222      * @return bool
00223      */
00224     protected function isOldStyleWorkspace() {
00225         $foundOldStyleStages = FALSE;
00226         $workspaces = $this->getWorkspacesWithoutStages();
00227         $workspacesWithReviewers = 0;
00228         $workspaceUids = array();
00229         foreach ($workspaces as $workspace) {
00230             $workspaceUids[] = $workspace['uid'];
00231             if ($workspace['reviewers']) {
00232                 $workspacesWithReviewers++;
00233             }
00234         }
00235         if (!$workspacesWithReviewers && !empty($workspaceUids)) {
00236             $tables = array_keys($GLOBALS['TCA']);
00237             foreach ($tables as $table) {
00238                 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
00239                 if ($versioningVer > 0) {
00240                     $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
00241                         'uid',
00242                         $table,
00243                         't3ver_wsid IN (' . implode(',', $workspaceUids) . ') AND t3ver_stage IN (-1,1,10) AND pid = -1'
00244                     );
00245                     if ($count > 0) {
00246                         $foundOldStyleStages = TRUE;
00247                         break;
00248                     }
00249                 }
00250             }
00251         }
00252         return $foundOldStyleStages || $workspacesWithReviewers;
00253     }
00254 
00255     /**
00256      * Create a new stage for the given workspace
00257      *
00258      * @param       integer Workspace ID
00259      * @param       string      The label of the new stage
00260      * @param       string      The users or groups which are authorized for that stage
00261      * @return  integer The id of the new stage
00262      */
00263     protected function createReviewStageForWorkspace($workspaceId, $stageLabel, $stageMembers) {
00264         $data = array(
00265             'parentid' => $workspaceId,
00266             'parenttable' => 'sys_workspace',
00267             'title' => $stageLabel,
00268             'responsible_persons' => $stageMembers
00269         );
00270         $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace_stage', $data);
00271         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00272         return $GLOBALS['TYPO3_DB']->sql_insert_id();
00273     }
00274 
00275     /**
00276      * Updates the stages of placeholder records within the given workspace from $oldId to $newId
00277      *
00278      * @param       integer Workspace ID
00279      * @param       integer Old stage od
00280      * @param       integer New stage od
00281      * @return  void
00282      */
00283     protected function migrateOldRecordsToStage($workspaceId, $oldStageId, $newStageId) {
00284         $tables = array_keys($GLOBALS['TCA']);
00285 
00286         $where = 't3ver_wsid = ' . intval($workspaceId) . ' AND t3ver_stage = ' . intval($oldStageId) . ' AND pid = -1';
00287         $values = array(
00288             't3ver_stage' => intval($newStageId)
00289         );
00290         foreach($tables as $table) {
00291             $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
00292             if ($versioningVer > 0) {
00293                 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
00294                 $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00295             }
00296         }
00297     }
00298 
00299 
00300     /**
00301      * Check if there's any workspace which doesn't support the new admin-field format yet
00302      * @return bool
00303      */
00304     protected function isOldStyleAdminFieldUsed() {
00305         $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
00306         $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
00307         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00308         return $count > 0;
00309     }
00310 
00311     /**
00312      * Create a real workspace named "Draft"
00313      *
00314      * @return integer
00315      */
00316     protected function createWorkspace() {
00317             // @todo who are the reviewers and owners for this workspace?
00318             // In previous versions this was defined in be_groups/be_users with the setting "Edit in Draft"
00319         $data = array(
00320             'title' => 'Draft'
00321         );
00322         $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace', $data);
00323         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00324         return $GLOBALS['TYPO3_DB']->sql_insert_id();
00325     }
00326 
00327     /**
00328      * Migrates all elements from the old draft workspace to the new one.
00329      *
00330      * @param  integer $wsId
00331      * @return void
00332      */
00333     protected function migrateDraftWorkspaceRecordsToWorkspace($wsId) {
00334         $tables = array_keys($GLOBALS['TCA']);
00335         $where = 't3ver_wsid=-1';
00336         $values = array(
00337             't3ver_wsid' => intval($wsId)
00338         );
00339         foreach($tables as $table) {
00340             $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
00341             if ($versioningVer > 0 && $this->hasElementsOnWorkspace($table, -1)) {
00342                 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
00343                 $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00344             }
00345         }
00346     }
00347 
00348     /**
00349      * Migrate all workspace adminusers fields to support groups aswell,
00350      * this means that the old comma separated list of uids (referring to be_users)
00351      * is updated to be a list of uids with the tablename as prefix
00352      *
00353      * @return void
00354      */
00355     protected function migrateAdminFieldToNewStyle() {
00356         $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
00357         $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, adminusers', 'sys_workspace', $where);
00358         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00359         foreach ($workspaces as $workspace) {
00360             $updateArray = array(
00361                 'adminusers' => 'be_users_' . implode(',be_users_', t3lib_div::trimExplode(',', $workspace['adminusers'], TRUE)),
00362             );
00363             $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
00364                 'sys_workspace',
00365                 'uid = "' . $workspace['uid'] . '"',
00366                 $updateArray
00367             );
00368             $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00369         }
00370     }
00371 
00372     /**
00373      * Includes the TCA definition of installed extensions.
00374      *
00375      * This method is used because usually the TCA is included within the init.php script, this doesn't happen
00376      * if the install-tool is used, therefore this has to be done by hand.
00377      *
00378      * @return void
00379      */
00380     protected function includeTCA() {
00381         global $TCA; // this is relevant because it's used within the included ext_tables.php files - do NOT remove it
00382 
00383         include_once(TYPO3_tables_script ? PATH_typo3conf . TYPO3_tables_script : PATH_t3lib . 'stddb/tables.php');
00384             // Extension additions
00385         if ($GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE']) {
00386             include_once(PATH_typo3conf . $GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE'] . '_ext_tables.php');
00387         } else {
00388             include_once(PATH_t3lib . 'stddb/load_ext_tables.php');
00389         }
00390     }
00391 
00392     /**
00393      * Determines whether a table has elements in a particular workspace.
00394      *
00395      * @param string $table Name of the table
00396      * @param integer $workspaceId Id of the workspace
00397      * @return boolean
00398      */
00399     protected function hasElementsOnWorkspace($table, $workspaceId) {
00400         $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
00401             'uid',
00402             $table,
00403             't3ver_wsid=' . intval($workspaceId)
00404         );
00405 
00406         $this->sqlQueries[] =  $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
00407 
00408         return ($count > 0);
00409     }
00410 
00411     /**
00412      * Returns all sys_workspace records which are not referenced by any sys_workspace_stages record
00413      *
00414      * @return array
00415      */
00416     protected function getWorkspacesWithoutStages() {
00417         $stages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('parentid', 'sys_workspace_stage', 'parenttable="sys_workspace"');
00418         $wsWhitelist = array();
00419         foreach ($stages as $stage) {
00420             $wsWhitelist[] = $stage['parentid'];
00421         }
00422         $where = 'deleted=0';
00423         $where .= (!empty($wsWhitelist) ? ' AND uid NOT IN (' . implode(',', $wsWhitelist) . ')' : '');
00424         return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_workspace', $where);
00425     }
00426 }
00427 ?>