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