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