[TASK] Remove unused property in Package object
[Packages/TYPO3.CMS.git] / typo3 / sysext / lowlevel / Classes / VersionsCommand.php
1 <?php
2 namespace TYPO3\CMS\Lowlevel;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
20 use TYPO3\CMS\Core\DataHandling\DataHandler;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\MathUtility;
23 use TYPO3\CMS\Core\Versioning\VersionState;
24
25 /**
26 * Looking for versions of records
27 */
28 class VersionsCommand extends CleanerCommand
29 {
30 /**
31 * Constructor
32 */
33 public function __construct()
34 {
35 parent::__construct();
36 // Setting up help:
37 $this->cli_options[] = ['--echotree level', 'When "level" is set to 1 or higher you will see the page of the page tree outputted as it is traversed. A value of 2 for "level" will show even more information.'];
38 $this->cli_options[] = ['--pid id', 'Setting start page in page tree. Default is the page tree root, 0 (zero)'];
39 $this->cli_options[] = ['--depth int', 'Setting traversal depth. 0 (zero) will only analyse start page (see --pid), 1 will traverse one level of subpages etc.'];
40 $this->cli_options[] = ['--flush-live', 'If set, not only published versions from Live workspace are flushed, but ALL versions from Live workspace (which are offline of course)'];
41 $this->cli_help['name'] = 'versions -- To find information about versions and workspaces in the system';
42 $this->cli_help['description'] = trim('
43 Traversing page tree and finding versions, categorizing them by various properties.
44 Published versions from the Live workspace are registered. So are all offline versions from Live workspace in general. Further, versions in non-existing workspaces are found.
45
46 Automatic Repair:
47 - Deleting (completely) published versions from LIVE workspace OR _all_ offline versions from Live workspace (toggle by --flush-live)
48 - Resetting workspace for versions where workspace is deleted. (You might want to run this tool again after this operation to clean out those new elements in the Live workspace)
49 - Deleting unused placeholders
50 ');
51 $this->cli_help['examples'] = '';
52 }
53
54 /**
55 * Find orphan records
56 * VERY CPU and memory intensive since it will look up the whole page tree!
57 *
58 * @return array
59 */
60 public function main()
61 {
62 // Initialize result array:
63 $resultArray = [
64 'message' => $this->cli_help['name'] . LF . LF . $this->cli_help['description'],
65 'headers' => [
66 'versions' => ['All versions', 'Showing all versions of records found', 0],
67 'versions_published' => ['All published versions', 'This is all records that has been published and can therefore be removed permanently', 1],
68 'versions_liveWS' => ['All versions in Live workspace', 'This is all records that are offline versions in the Live workspace. You may wish to flush these if you only use workspaces for versioning since then you might find lots of versions piling up in the live workspace which have simply been disconnected from the workspace before they were published.', 1],
69 'versions_lost_workspace' => ['Versions outside a workspace', 'Versions that has lost their connection to a workspace in TYPO3.', 3],
70 'versions_inside_versioned_page' => ['Versions in versions', 'Versions inside an already versioned page. Something that is confusing to users and therefore should not happen but is technically possible.', 2],
71 'versions_unused_placeholders' => ['Unused placeholder records', 'Placeholder records which are not used anymore by offline versions.', 2],
72 'versions_move_placeholders_ok' => ['Move placeholders', 'Move-to placeholder records which has good integrity', 0],
73 'versions_move_placeholders_bad' => ['Move placeholders with bad integrity', 'Move-to placeholder records which has bad integrity', 2],
74 'versions_move_id_check' => ['Checking if t3ver_move_id is correct', 't3ver_move_id must only be set with online records having t3ver_state=3.', 2]
75 ],
76 'versions' => []
77 ];
78 $startingPoint = $this->cli_isArg('--pid') ? MathUtility::forceIntegerInRange($this->cli_argValue('--pid'), 0) : 0;
79 $depth = $this->cli_isArg('--depth') ? MathUtility::forceIntegerInRange($this->cli_argValue('--depth'), 0) : 1000;
80 $this->genTree($startingPoint, $depth, (int)$this->cli_argValue('--echotree'));
81 $resultArray['versions'] = $this->recStats['versions'];
82 $resultArray['versions_published'] = $this->recStats['versions_published'];
83 $resultArray['versions_liveWS'] = $this->recStats['versions_liveWS'];
84 $resultArray['versions_lost_workspace'] = $this->recStats['versions_lost_workspace'];
85 $resultArray['versions_inside_versioned_page'] = $this->recStats['versions_inside_versioned_page'];
86 // Finding all placeholders with no records attached!
87 $resultArray['versions_unused_placeholders'] = [];
88 foreach ($GLOBALS['TCA'] as $table => $cfg) {
89 if ($cfg['ctrl']['versioningWS']) {
90 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
91 ->getQueryBuilderForTable($table);
92
93 $queryBuilder->getRestrictions()
94 ->removeAll()
95 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
96
97 $result = $queryBuilder
98 ->select('uid', 'pid')
99 ->from($table)
100 ->where(
101 $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
102 $queryBuilder->expr()->eq(
103 't3ver_state',
104 $queryBuilder->createNamedParameter(
105 (string)new VersionState(VersionState::NEW_PLACEHOLDER),
106 \PDO::PARAM_INT
107 )
108 )
109 )
110 ->execute();
111
112 while ($placeholderRecord = $result->fetch()) {
113 if (count(BackendUtility::selectVersionsOfRecord($table, $placeholderRecord['uid'], 'uid', '*', null)) <= 1) {
114 $resultArray['versions_unused_placeholders'][GeneralUtility::shortMD5($table . ':' . $placeholderRecord['uid'])] = $table . ':' . $placeholderRecord['uid'];
115 }
116 }
117 }
118 }
119 asort($resultArray['versions_unused_placeholders']);
120 // Finding all move placeholders with inconsistencies:
121 $resultArray['versions_move_placeholders_ok'] = [];
122 $resultArray['versions_move_placeholders_bad'] = [];
123 foreach ($GLOBALS['TCA'] as $table => $cfg) {
124 if (BackendUtility::isTableWorkspaceEnabled($table)) {
125 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
126 ->getQueryBuilderForTable($table);
127
128 $queryBuilder->getRestrictions()
129 ->removeAll()
130 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
131
132 $result = $queryBuilder
133 ->select('uid', 'pid', 't3ver_move_id', 't3ver_wsid', 't3ver_state')
134 ->from($table)
135 ->where(
136 $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
137 $queryBuilder->expr()->eq(
138 't3ver_state',
139 $queryBuilder->createNamedParameter(
140 (string)new VersionState(VersionState::MOVE_PLACEHOLDER),
141 \PDO::PARAM_INT
142 )
143 )
144 )
145 ->execute();
146 while ($placeholderRecord = $result->fetch()) {
147 $shortID = GeneralUtility::shortMD5($table . ':' . $placeholderRecord['uid']);
148 if ((int)$placeholderRecord['t3ver_wsid'] !== 0) {
149 $phrecCopy = $placeholderRecord;
150 if (BackendUtility::movePlhOL($table, $placeholderRecord)) {
151 if ($wsAlt = BackendUtility::getWorkspaceVersionOfRecord($phrecCopy['t3ver_wsid'], $table, $placeholderRecord['uid'], 'uid,pid,t3ver_state')) {
152 if (!VersionState::cast($wsAlt['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
153 $resultArray['versions_move_placeholders_bad'][$shortID] = [$table . ':' . $placeholderRecord['uid'], 'State for version was not "4" as it should be!', $phrecCopy];
154 } else {
155 $resultArray['versions_move_placeholders_ok'][$shortID] = [
156 $table . ':' . $placeholderRecord['uid'],
157 'PLH' => $phrecCopy,
158 'online' => $placeholderRecord,
159 'PNT' => $wsAlt
160 ];
161 }
162 } else {
163 $resultArray['versions_move_placeholders_bad'][$shortID] = [$table . ':' . $placeholderRecord['uid'], 'No version was found for online record to be moved. A version must exist.', $phrecCopy];
164 }
165 } else {
166 $resultArray['versions_move_placeholders_bad'][$shortID] = [$table . ':' . $placeholderRecord['uid'], 'Did not find online record for "t3ver_move_id" value ' . $placeholderRecord['t3ver_move_id'], $placeholderRecord];
167 }
168 } else {
169 $resultArray['versions_move_placeholders_bad'][$shortID] = [$table . ':' . $placeholderRecord['uid'], 'Placeholder was not assigned a workspace value in t3ver_wsid.', $placeholderRecord];
170 }
171 }
172 }
173 }
174 ksort($resultArray['versions_move_placeholders_ok']);
175 ksort($resultArray['versions_move_placeholders_bad']);
176 // Finding move_id_check inconsistencies:
177 $resultArray['versions_move_id_check'] = [];
178 foreach ($GLOBALS['TCA'] as $table => $cfg) {
179 if (BackendUtility::isTableWorkspaceEnabled($table)) {
180 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
181 ->getQueryBuilderForTable($table);
182
183 $queryBuilder->getRestrictions()
184 ->removeAll()
185 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
186
187 $result = $queryBuilder
188 ->select('uid', 'pid', 't3ver_move_id', 't3ver_wsid', 't3ver_state')
189 ->from($table)
190 ->where(
191 $queryBuilder->expr()->neq(
192 't3ver_move_id',
193 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
194 )
195 )
196 ->execute();
197
198 while ($placeholderRecord = $result->fetch()) {
199 if (VersionState::cast($placeholderRecord['t3ver_state'])->equals(VersionState::MOVE_PLACEHOLDER)) {
200 if ($placeholderRecord['pid'] != -1) {
201 } else {
202 $resultArray['versions_move_id_check'][] = [$table . ':' . $placeholderRecord['uid'], 'Record was offline, must not be!', $placeholderRecord];
203 }
204 } else {
205 $resultArray['versions_move_id_check'][] = [$table . ':' . $placeholderRecord['uid'], 'Record had t3ver_move_id set to "' . $placeholderRecord['t3ver_move_id'] . '" while having t3ver_state=' . $placeholderRecord['t3ver_state'], $placeholderRecord];
206 }
207 }
208 }
209 }
210 return $resultArray;
211 }
212
213 /**
214 * Mandatory autofix function
215 * Will run auto-fix on the result array. Echos status during processing.
216 *
217 * @param array $resultArray Result array from main() function
218 * @return void
219 */
220 public function main_autoFix($resultArray)
221 {
222 $kk = $this->cli_isArg('--flush-live') ? 'versions_liveWS' : 'versions_published';
223 // Putting "pages" table in the bottom:
224 if (isset($resultArray[$kk]['pages'])) {
225 $_pages = $resultArray[$kk]['pages'];
226 unset($resultArray[$kk]['pages']);
227 $resultArray[$kk]['pages'] = $_pages;
228 }
229 // Traversing records:
230 foreach ($resultArray[$kk] as $table => $list) {
231 echo 'Flushing published records from table "' . $table . '":' . LF;
232 foreach ($list as $uid) {
233 echo ' Flushing record "' . $table . ':' . $uid . '": ';
234 if ($bypass = $this->cli_noExecutionCheck($table . ':' . $uid)) {
235 echo $bypass;
236 } else {
237 // Execute CMD array:
238 $tce = GeneralUtility::makeInstance(DataHandler::class);
239 $tce->start([], []);
240 $tce->deleteEl($table, $uid, true, true);
241 // Return errors if any:
242 if (count($tce->errorLog)) {
243 echo ' ERROR from "DataHandler":' . LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $tce->errorLog);
244 } else {
245 echo 'DONE';
246 }
247 }
248 echo LF;
249 }
250 }
251 // Traverse workspace:
252 foreach ($resultArray['versions_lost_workspace'] as $table => $list) {
253 echo 'Resetting workspace to zero for records from table "' . $table . '":' . LF;
254 foreach ($list as $uid) {
255 echo ' Flushing record "' . $table . ':' . $uid . '": ';
256 if ($bypass = $this->cli_noExecutionCheck($table . ':' . $uid)) {
257 echo $bypass;
258 } else {
259 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
260 ->getQueryBuilderForTable($table);
261
262 $queryBuilder
263 ->update($table)
264 ->where(
265 $queryBuilder->expr()->eq(
266 'uid',
267 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
268 )
269 )
270 ->set('t3ver_wsid', 0)
271 ->execute();
272 echo 'DONE';
273 }
274 echo LF;
275 }
276 }
277 // Delete unused placeholders
278 foreach ($resultArray['versions_unused_placeholders'] as $recID) {
279 list($table, $uid) = explode(':', $recID);
280 echo 'Deleting unused placeholder (soft) "' . $table . ':' . $uid . '": ';
281 if ($bypass = $this->cli_noExecutionCheck($table . ':' . $uid)) {
282 echo $bypass;
283 } else {
284 // Execute CMD array:
285 $tce = GeneralUtility::makeInstance(DataHandler::class);
286 $tce->start([], []);
287 $tce->deleteAction($table, $uid);
288 // Return errors if any:
289 if (count($tce->errorLog)) {
290 echo ' ERROR from "DataHandler":' . LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $tce->errorLog);
291 } else {
292 echo 'DONE';
293 }
294 }
295 echo LF;
296 }
297 }
298 }