Fixed issue #17260: Installation of version/workspaces cannot be skipped in migration...
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / updates / class.tx_coreupdates_migrateworkspaces.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Tolleiv Nietsch <info@tolleiv.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28
29 /**
30 * Migrates workspaces from TYPO3 versions below 4.5.
31 *
32 * @author Tolleiv Nietsch <info@tolleiv.de>
33 * @version $Id$
34 */
35 class tx_coreupdates_migrateworkspaces extends tx_coreupdates_installsysexts {
36 protected $title = 'Versioning and Workspaces';
37
38 public $sqlQueries;
39
40 /**
41 * Checks if an update is needed
42 *
43 * @param string &$description: The description for the update, which will be updated with a description of the script's purpose
44 * @return boolean whether an update is needed (true) or not (false)
45 */
46 public function checkForUpdate(&$description) {
47 $result = FALSE;
48 $description = 'Migrates the old hardcoded draft workspace to be a real workspace record,
49 updates workspace owner fields to support either users or groups and
50 migrates the old-style workspaces with fixed workflow to a custom-stage workflow. If required
51 the extbase, fluid, version and workspaces extensions are installed.';
52
53 $reason = '';
54 // TYPO3 version 4.5 and above
55 if ($this->versionNumber >= 4005000) {
56 // If neither version nor workspaces is installed, we're not doing a migration
57 // Present the user with the choice of activating versioning and workspaces
58 if (!t3lib_extMgm::isLoaded('version') && !t3lib_extMgm::isLoaded('workspaces')) {
59 $result = TRUE;
60 // Override the default description
61 $description = 'Activates the usage of workspaces in your installation. Workspaces let you edit elements
62 without the changes being visible on the live web site right away. Modified elements can then go
63 through a validation process and eventually be published.<br /><br />';
64 $description .= 'This wizard will install system extensions "version" and "workspaces" (and may
65 install "fluid" and "extbase" too, as they are used by the "workspaces" extension).';
66 } else {
67
68 $this->includeTCA();
69
70 if (!t3lib_extMgm::isLoaded('version') || !t3lib_extMgm::isLoaded('workspaces')) {
71 $result = TRUE;
72 $reason .= ' Both extensions "version" and "workspaces" need to be
73 present to use the entire versioning and workflow featureset of TYPO3.';
74 }
75
76 $tables = array_keys($GLOBALS['TYPO3_DB']->admin_get_tables());
77 // sys_workspace table might not exists if version extension was never installed
78 if (!in_array('sys_workspace', $tables) || !in_array('sys_workspace_stage', $tables)) {
79 $result = TRUE;
80 $reason .= ' The database tables for the workspace functionality are missing.';
81 } elseif ($this->isOldStyleAdminFieldUsed() || $this->isOldStyleWorkspace()) {
82 $wsCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', '');
83 $result |= $wsCount > 0;
84 $reason .= ' The existing workspaces will be checked for compatibility with the new features.';
85 }
86
87 $draftWorkspaceTestResult = $this->isDraftWorkspaceUsed();
88 if ($draftWorkspaceTestResult) {
89 $reason .= ' The old style draft workspace is used.
90 Related records will be moved into a full featured workspace.';
91 $result = TRUE;
92 }
93
94 $description .= '<br /><strong>Why do you need this wizard?</strong><br />' . $reason;
95 }
96 }
97
98 return $result;
99 }
100
101 /**
102 * This method requests input from the user about the upgrade process, if needed
103 *
104 * @param string $inputPrefix
105 * @return void
106 */
107 public function getUserInput($inputPrefix) {
108 $content = '';
109
110 if (!t3lib_extMgm::isLoaded('version') && !t3lib_extMgm::isLoaded('workspaces')) {
111 // We need feedback only if versioning is not activated at all
112 // In such a case we want to leave the user with the choice of not activating the stuff at all
113 $content = '
114 <fieldset>
115 <ol>
116 ';
117
118 $content .= '
119 <li class="labelAfter">
120 <input type="checkbox" id="versioning" name="' . $inputPrefix . '[versioning]" value="1" checked="checked" />
121 <label for="versioning">Activate workspaces?</label>
122 </li>
123 ';
124
125 $content .= '
126 </ol>
127 </fieldset>
128 ';
129
130 } else {
131 // No feedback needed, just include the update flag as a hidden field
132 $content = '<input type="hidden" id="versioning" name="' . $inputPrefix . '[versioning]" value="1" />';
133 }
134
135 return $content;
136 }
137
138 /**
139 * Performs the database update. Changes existing workspaces to use the new custom workspaces
140 *
141 * @param array &$databaseQueries: queries done in this update
142 * @param mixed &$customMessages: custom messages
143 * @return boolean whether it worked (true) or not (false)
144 */
145 public function performUpdate(array &$databaseQueries, &$customMessages) {
146 $result = TRUE;
147
148 // TYPO3 version below 4.5
149 if ($this->versionNumber < 4005000) {
150 return FALSE;
151 }
152 // Wizard skipped by the user
153 if (empty($this->pObj->INSTALL['update']['migrateWorkspaces']['versioning'])) {
154 return TRUE;
155 }
156
157 // There's no TCA available yet
158 $this->includeTCA();
159
160 // install version and workspace extension (especially when updating from very old TYPO3 versions
161 $this->installExtensions(array('extbase', 'fluid', 'version', 'workspaces'));
162
163 // migrate all workspaces to support groups and be_users
164 if ($this->isOldStyleAdminFieldUsed()) {
165 $this->migrateAdminFieldToNewStyle();
166 }
167
168 // create a new dedicated "Draft" workspace and move all records to that new workspace
169 if ($this->isDraftWorkspaceUsed()) {
170 $draftWorkspaceId = $this->createWorkspace();
171 if (is_integer($draftWorkspaceId)) {
172 $this->migrateDraftWorkspaceRecordsToWorkspace($draftWorkspaceId);
173 }
174 }
175
176 $workspaces = $this->getWorkspacesWithoutStages();
177 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
178 $label = 'Review';
179 foreach($workspaces as $workspace) {
180 // Find all workspaces and add "review" stage record
181 // Add review users and groups to the new IRRE record
182 $reviewStageId = $this->createReviewStageForWorkspace($workspace['uid'], $label, $workspace['reviewers']);
183 // Update all "review" state records in the database to point to the new state
184 $this->migrateOldRecordsToStage($workspace['uid'], 1, $reviewStageId);
185 // Update all "ready to publish" records in the database to point to the new ready to publish state
186 $this->migrateOldRecordsToStage($workspace['uid'], 10, -99);
187 }
188
189 if (is_array($this->sqlQueries) && is_array($databaseQueries)) {
190 $databaseQueries = array_merge($databaseQueries, $this->sqlQueries);
191 }
192
193 return $result;
194 }
195
196 /**
197 * Check if any table contains draft-workspace records
198 *
199 * @return bool
200 */
201 protected function isDraftWorkspaceUsed() {
202 $foundDraftRecords = FALSE;
203
204 $tables = array_keys($GLOBALS['TCA']);
205 foreach ($tables as $table) {
206 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
207 if ($versioningVer > 0) {
208 if ($this->hasElementsOnWorkspace($table, -1)) {
209 $foundDraftRecords = TRUE;
210 break;
211 }
212 }
213 }
214
215 return $foundDraftRecords;
216 }
217
218 /**
219 * Find workspaces which have no sys_workspace_state(s) but have records using states
220 * If "
221 *
222 * @return bool
223 */
224 protected function isOldStyleWorkspace() {
225 $foundOldStyleStages = FALSE;
226 $workspaces = $this->getWorkspacesWithoutStages();
227 $workspacesWithReviewers = 0;
228 $workspaceUids = array();
229 foreach ($workspaces as $workspace) {
230 $workspaceUids[] = $workspace['uid'];
231 if ($workspace['reviewers']) {
232 $workspacesWithReviewers++;
233 }
234 }
235 if (!$workspacesWithReviewers && !empty($workspaceUids)) {
236 $tables = array_keys($GLOBALS['TCA']);
237 foreach ($tables as $table) {
238 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
239 if ($versioningVer > 0) {
240 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
241 'uid',
242 $table,
243 't3ver_wsid IN (' . implode(',', $workspaceUids) . ') AND t3ver_stage IN (-1,1,10) AND pid = -1'
244 );
245 if ($count > 0) {
246 $foundOldStyleStages = TRUE;
247 break;
248 }
249 }
250 }
251 }
252 return $foundOldStyleStages || $workspacesWithReviewers;
253 }
254
255 /**
256 * Create a new stage for the given workspace
257 *
258 * @param integer Workspace ID
259 * @param string The label of the new stage
260 * @param string The users or groups which are authorized for that stage
261 * @return integer The id of the new stage
262 */
263 protected function createReviewStageForWorkspace($workspaceId, $stageLabel, $stageMembers) {
264 $data = array(
265 'parentid' => $workspaceId,
266 'parenttable' => 'sys_workspace',
267 'title' => $stageLabel,
268 'responsible_persons' => $stageMembers
269 );
270 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace_stage', $data);
271 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
272 return $GLOBALS['TYPO3_DB']->sql_insert_id();
273 }
274
275 /**
276 * Updates the stages of placeholder records within the given workspace from $oldId to $newId
277 *
278 * @param integer Workspace ID
279 * @param integer Old stage od
280 * @param integer New stage od
281 * @return void
282 */
283 protected function migrateOldRecordsToStage($workspaceId, $oldStageId, $newStageId) {
284 $tables = array_keys($GLOBALS['TCA']);
285
286 $where = 't3ver_wsid = ' . intval($workspaceId) . ' AND t3ver_stage = ' . intval($oldStageId) . ' AND pid = -1';
287 $values = array(
288 't3ver_stage' => intval($newStageId)
289 );
290 foreach($tables as $table) {
291 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
292 if ($versioningVer > 0) {
293 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
294 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
295 }
296 }
297 }
298
299
300 /**
301 * Check if there's any workspace which doesn't support the new admin-field format yet
302 * @return bool
303 */
304 protected function isOldStyleAdminFieldUsed() {
305 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
306 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
307 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
308 return $count > 0;
309 }
310
311 /**
312 * Create a real workspace named "Draft"
313 *
314 * @return integer
315 */
316 protected function createWorkspace() {
317 // @todo who are the reviewers and owners for this workspace?
318 // In previous versions this was defined in be_groups/be_users with the setting "Edit in Draft"
319 $data = array(
320 'title' => 'Draft'
321 );
322 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace', $data);
323 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
324 return $GLOBALS['TYPO3_DB']->sql_insert_id();
325 }
326
327 /**
328 * Migrates all elements from the old draft workspace to the new one.
329 *
330 * @param integer $wsId
331 * @return void
332 */
333 protected function migrateDraftWorkspaceRecordsToWorkspace($wsId) {
334 $tables = array_keys($GLOBALS['TCA']);
335 $where = 't3ver_wsid=-1';
336 $values = array(
337 't3ver_wsid' => intval($wsId)
338 );
339 foreach($tables as $table) {
340 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
341 if ($versioningVer > 0 && $this->hasElementsOnWorkspace($table, -1)) {
342 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
343 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
344 }
345 }
346 }
347
348 /**
349 * Migrate all workspace adminusers fields to support groups aswell,
350 * this means that the old comma separated list of uids (referring to be_users)
351 * is updated to be a list of uids with the tablename as prefix
352 *
353 * @return void
354 */
355 protected function migrateAdminFieldToNewStyle() {
356 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
357 $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, adminusers', 'sys_workspace', $where);
358 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
359 foreach ($workspaces as $workspace) {
360 $updateArray = array(
361 'adminusers' => 'be_users_' . implode(',be_users_', t3lib_div::trimExplode(',', $workspace['adminusers'], TRUE)),
362 );
363 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
364 'sys_workspace',
365 'uid = "' . $workspace['uid'] . '"',
366 $updateArray
367 );
368 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
369 }
370 }
371
372 /**
373 * Includes the TCA definition of installed extensions.
374 *
375 * This method is used because usually the TCA is included within the init.php script, this doesn't happen
376 * if the install-tool is used, therefore this has to be done by hand.
377 *
378 * @return void
379 */
380 protected function includeTCA() {
381 global $TCA; // this is relevant because it's used within the included ext_tables.php files - do NOT remove it
382
383 include_once(TYPO3_tables_script ? PATH_typo3conf . TYPO3_tables_script : PATH_t3lib . 'stddb/tables.php');
384 // Extension additions
385 if ($GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE']) {
386 include_once(PATH_typo3conf . $GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE'] . '_ext_tables.php');
387 } else {
388 include_once(PATH_t3lib . 'stddb/load_ext_tables.php');
389 }
390 }
391
392 /**
393 * Determines whether a table has elements in a particular workspace.
394 *
395 * @param string $table Name of the table
396 * @param integer $workspaceId Id of the workspace
397 * @return boolean
398 */
399 protected function hasElementsOnWorkspace($table, $workspaceId) {
400 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
401 'uid',
402 $table,
403 't3ver_wsid=' . intval($workspaceId)
404 );
405
406 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
407
408 return ($count > 0);
409 }
410
411 /**
412 * Returns all sys_workspace records which are not referenced by any sys_workspace_stages record
413 *
414 * @return array
415 */
416 protected function getWorkspacesWithoutStages() {
417 $stages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('parentid', 'sys_workspace_stage', 'parenttable="sys_workspace"');
418 $wsWhitelist = array();
419 foreach ($stages as $stage) {
420 $wsWhitelist[] = $stage['parentid'];
421 }
422 $where = 'deleted=0';
423 $where .= (!empty($wsWhitelist) ? ' AND uid NOT IN (' . implode(',', $wsWhitelist) . ')' : '');
424 return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_workspace', $where);
425 }
426 }
427 ?>