529b9c8cb1f9bfb107c9141cf36000898cfcd22a
[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
57 $this->includeTCA();
58
59 if(!t3lib_extMgm::isLoaded('version') || !t3lib_extMgm::isLoaded('workspaces')) {
60 $result = TRUE;
61 $reason .= ' The extensions "version" and "workspaces" need to be
62 present to use the entire versioning and workflow featureset of TYPO3.';
63 }
64
65 $tables = array_keys($GLOBALS['TYPO3_DB']->admin_get_tables());
66 // sys_workspace table might not exists if version extension was never installed
67 if (!in_array('sys_workspace', $tables) || !in_array('sys_workspace_stage', $tables)) {
68 $result = TRUE;
69 $reason .= ' The database tables for the workspace functionality are missing.';
70 } elseif ($this->isOldStyleAdminFieldUsed() || $this->isOldStyleWorkspace()) {
71 $wsCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', '');
72 $result |= $wsCount > 0;
73 $reason .= ' The existing workspaces will be checked for compatibility with the new features.';
74 }
75
76 $draftWorkspaceTestResult = $this->isDraftWorkspaceUsed();
77 if ($draftWorkspaceTestResult) {
78 $reason .= ' The old style draft workspace is used.
79 Related records will be moved into a full featured workspace.';
80 $result = TRUE;
81 }
82
83 $description .= '<br /><strong>Why do you need this wizard?</strong><br />' . $reason;
84 }
85
86 return $result;
87 }
88
89 /**
90 * If there's any user-input which we'd love to process, this method allows to specify what
91 * kind of input we need.
92 *
93 * Since we don't need input this is left empty. It's still here to avoid that this is inherited.
94 *
95 * @param string $inputPrefix
96 * @return void
97 */
98 public function getUserInput($inputPrefix) {
99 return;
100 }
101
102 /**
103 * Performs the database update. Changes existing workspaces to use the new custom workspaces
104 *
105 * @param array &$databaseQueries: queries done in this update
106 * @param mixed &$customMessages: custom messages
107 * @return boolean whether it worked (true) or not (false)
108 */
109 public function performUpdate(array &$databaseQueries, &$customMessages) {
110 $result = TRUE;
111
112 // TYPO3 version below 4.5
113 if ($this->versionNumber < 4005000) {
114 return FALSE;
115 }
116
117 // There's no TCA available yet
118 $this->includeTCA();
119
120 // install version and workspace extension (especially when updating from very old TYPO3 versions
121 $this->installExtensions(array('extbase', 'fluid', 'version', 'workspaces'));
122
123 // migrate all workspaces to support groups and be_users
124 if ($this->isOldStyleAdminFieldUsed()) {
125 $this->migrateAdminFieldToNewStyle();
126 }
127
128 // create a new dedicated "Draft" workspace and move all records to that new workspace
129 if ($this->isDraftWorkspaceUsed()) {
130 $draftWorkspaceId = $this->createWorkspace();
131 if (is_integer($draftWorkspaceId)) {
132 $this->migrateDraftWorkspaceRecordsToWorkspace($draftWorkspaceId);
133 }
134 }
135
136 $workspaces = $this->getWorkspacesWithoutStages();
137 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
138 $label = 'Review';
139 foreach($workspaces as $workspace) {
140 // Find all workspaces and add "review" stage record
141 // Add review users and groups to the new IRRE record
142 $reviewStageId = $this->createReviewStageForWorkspace($workspace['uid'], $label, $workspace['reviewers']);
143 // Update all "review" state records in the database to point to the new state
144 $this->migrateOldRecordsToStage($workspace['uid'], 1, $reviewStageId);
145 // Update all "ready to publish" records in the database to point to the new ready to publish state
146 $this->migrateOldRecordsToStage($workspace['uid'], 10, -99);
147 }
148
149 if (is_array($this->sqlQueries) && is_array($databaseQueries)) {
150 $databaseQueries = array_merge($databaseQueries, $this->sqlQueries);
151 }
152
153 return $result;
154 }
155
156 /**
157 * Check if any table contains draft-workspace records
158 *
159 * @return bool
160 */
161 protected function isDraftWorkspaceUsed() {
162 $foundDraftRecords = FALSE;
163
164 $tables = array_keys($GLOBALS['TCA']);
165 foreach ($tables as $table) {
166 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
167 if ($versioningVer > 0) {
168 if ($this->hasElementsOnWorkspace($table, -1)) {
169 $foundDraftRecords = TRUE;
170 break;
171 }
172 }
173 }
174
175 return $foundDraftRecords;
176 }
177
178 /**
179 * Find workspaces which have no sys_workspace_state(s) but have records using states
180 * If "
181 *
182 * @return bool
183 */
184 protected function isOldStyleWorkspace() {
185 $foundOldStyleStages = FALSE;
186 $workspaces = $this->getWorkspacesWithoutStages();
187 $workspacesWithReviewers = 0;
188 $workspaceUids = array();
189 foreach ($workspaces as $workspace) {
190 $workspaceUids[] = $workspace['uid'];
191 if ($workspace['reviewers']) {
192 $workspacesWithReviewers++;
193 }
194 }
195 if (!$workspacesWithReviewers && !empty($workspaceUids)) {
196 $tables = array_keys($GLOBALS['TCA']);
197 foreach ($tables as $table) {
198 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
199 if ($versioningVer > 0) {
200 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
201 'uid',
202 $table,
203 't3ver_wsid IN (' . implode(',', $workspaceUids) . ') AND t3ver_stage IN (-1,1,10) AND pid = -1'
204 );
205 if ($count > 0) {
206 $foundOldStyleStages = TRUE;
207 break;
208 }
209 }
210 }
211 }
212 return $foundOldStyleStages || $workspacesWithReviewers;
213 }
214
215 /**
216 * Create a new stage for the given workspace
217 *
218 * @param integer Workspace ID
219 * @param string The label of the new stage
220 * @param string The users or groups which are authorized for that stage
221 * @return integer The id of the new stage
222 */
223 protected function createReviewStageForWorkspace($workspaceId, $stageLabel, $stageMembers) {
224 $data = array(
225 'parentid' => $workspaceId,
226 'parenttable' => 'sys_workspace',
227 'title' => $stageLabel,
228 'responsible_persons' => $stageMembers
229 );
230 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace_stage', $data);
231 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
232 return $GLOBALS['TYPO3_DB']->sql_insert_id();
233 }
234
235 /**
236 * Updates the stages of placeholder records within the given workspace from $oldId to $newId
237 *
238 * @param integer Workspace ID
239 * @param integer Old stage od
240 * @param integer New stage od
241 * @return void
242 */
243 protected function migrateOldRecordsToStage($workspaceId, $oldStageId, $newStageId) {
244 $tables = array_keys($GLOBALS['TCA']);
245
246 $where = 't3ver_wsid = ' . intval($workspaceId) . ' AND t3ver_stage = ' . intval($oldStageId) . ' AND pid = -1';
247 $values = array(
248 't3ver_stage' => intval($newStageId)
249 );
250 foreach($tables as $table) {
251 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
252 if ($versioningVer > 0) {
253 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
254 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
255 }
256 }
257 }
258
259
260 /**
261 * Check if there's any workspace which doesn't support the new admin-field format yet
262 * @return bool
263 */
264 protected function isOldStyleAdminFieldUsed() {
265 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
266 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
267 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
268 return $count > 0;
269 }
270
271 /**
272 * Create a real workspace named "Draft"
273 *
274 * @return integer
275 */
276 protected function createWorkspace() {
277 // @todo who are the reviewers and owners for this workspace?
278 // In previous versions this was defined in be_groups/be_users with the setting "Edit in Draft"
279 $data = array(
280 'title' => 'Draft'
281 );
282 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_workspace', $data);
283 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
284 return $GLOBALS['TYPO3_DB']->sql_insert_id();
285 }
286
287 /**
288 * Migrates all elements from the old draft workspace to the new one.
289 *
290 * @param integer $wsId
291 * @return void
292 */
293 protected function migrateDraftWorkspaceRecordsToWorkspace($wsId) {
294 $tables = array_keys($GLOBALS['TCA']);
295 $where = 't3ver_wsid=-1';
296 $values = array(
297 't3ver_wsid' => intval($wsId)
298 );
299 foreach($tables as $table) {
300 $versioningVer = t3lib_div::intInRange($GLOBALS['TCA'][$table]['ctrl']['versioningWS'], 0, 2, 0);
301 if ($versioningVer > 0 && $this->hasElementsOnWorkspace($table, -1)) {
302 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, $where, $values);
303 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
304 }
305 }
306 }
307
308 /**
309 * Migrate all workspace adminusers fields to support groups aswell,
310 * this means that the old comma separated list of uids (referring to be_users)
311 * is updated to be a list of uids with the tablename as prefix
312 *
313 * @return void
314 */
315 protected function migrateAdminFieldToNewStyle() {
316 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
317 $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, adminusers', 'sys_workspace', $where);
318 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
319 foreach ($workspaces as $workspace) {
320 $updateArray = array(
321 'adminusers' => 'be_users_' . implode(',be_users_', t3lib_div::trimExplode(',', $workspace['adminusers'], TRUE)),
322 );
323 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
324 'sys_workspace',
325 'uid = "' . $workspace['uid'] . '"',
326 $updateArray
327 );
328 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
329 }
330 }
331
332 /**
333 * Includes the TCA definition of installed extensions.
334 *
335 * This method is used because usually the TCA is included within the init.php script, this doesn't happen
336 * if the install-tool is used, therefore this has to be done by hand.
337 *
338 * @return void
339 */
340 protected function includeTCA() {
341 global $TCA; // this is relevant because it's used within the included ext_tables.php files - do NOT remove it
342
343 include_once(TYPO3_tables_script ? PATH_typo3conf . TYPO3_tables_script : PATH_t3lib . 'stddb/tables.php');
344 // Extension additions
345 if ($GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE']) {
346 include_once(PATH_typo3conf . $GLOBALS['TYPO3_LOADED_EXT']['_CACHEFILE'] . '_ext_tables.php');
347 } else {
348 include_once(PATH_t3lib . 'stddb/load_ext_tables.php');
349 }
350 }
351
352 /**
353 * Determines whether a table has elements in a particular workspace.
354 *
355 * @param string $table Name of the table
356 * @param integer $workspaceId Id of the workspace
357 * @return boolean
358 */
359 protected function hasElementsOnWorkspace($table, $workspaceId) {
360 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
361 'uid',
362 $table,
363 't3ver_wsid=' . intval($workspaceId)
364 );
365
366 $this->sqlQueries[] = $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery;
367
368 return ($count > 0);
369 }
370
371 /**
372 * Returns all sys_workspace records which are not referenced by any sys_workspace_stages record
373 *
374 * @return array
375 */
376 protected function getWorkspacesWithoutStages() {
377 $stages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('parentid', 'sys_workspace_stage', 'parenttable="sys_workspace"');
378 $wsWhitelist = array();
379 foreach ($stages as $stage) {
380 $wsWhitelist[] = $stage['parentid'];
381 }
382 $where = 'deleted=0';
383 $where .= (!empty($wsWhitelist) ? ' AND uid NOT IN (' . implode(',', $wsWhitelist) . ')' : '');
384 return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_workspace', $where);
385 }
386 }
387 ?>