[TASK] Move RecordHistory into DataHandling namespace
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / SeparateSysHistoryFromSysLogUpdate.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Install\Updates;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Merge data stored in sys_log that belongs to sys_history
25 */
26 class SeparateSysHistoryFromSysLogUpdate extends AbstractUpdate
27 {
28 /**
29 * @var string
30 */
31 protected $title = 'Migrates existing sys_log entries into sys_history';
32
33 /**
34 * Checks if an update is needed
35 *
36 * @param string $description The description for the update
37 *
38 * @return bool Whether an update is needed (true) or not (false)
39 */
40 public function checkForUpdate(&$description)
41 {
42 if ($this->isWizardDone()) {
43 return false;
44 }
45
46 // sys_log field has been removed, no need to do something.
47 if (!$this->checkIfFieldInTableExists('sys_history', 'sys_log_uid')) {
48 return false;
49 }
50
51 $description = 'The history of changes of a record is now solely stored within sys_history. Previous data within sys_log needs to be'
52 . ' migrated into sys_history now.</p>';
53
54 // Check if there is data to migrate
55 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
56 ->getQueryBuilderForTable('sys_history');
57 $queryBuilder->getRestrictions()->removeAll();
58 $count = $queryBuilder->count('*')
59 ->from('sys_history')
60 ->where($queryBuilder->expr()->neq('sys_log_uid', 0))
61 ->execute()
62 ->fetchColumn(0);
63
64 return $count > 0;
65 }
66
67 /**
68 * Moves data from sys_log into sys_history
69 * where a reference is still there: sys_history.sys_log_uid > 0
70 *
71 * @param array $databaseQueries Queries done in this update
72 * @param string $customMessage Custom messages
73 * @return bool
74 * @throws \Doctrine\DBAL\DBALException
75 */
76 public function performUpdate(array &$databaseQueries, &$customMessage)
77 {
78 // update "modify" statements (= decoupling)
79 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_history');
80 $queryBuilder = $connection->createQueryBuilder();
81 $rows = $queryBuilder
82 ->select('sys_history.uid AS history_uid', 'sys_history.history_data', 'sys_log.*')
83 ->from('sys_history')
84 ->leftJoin(
85 'sys_history',
86 'sys_log',
87 'sys_log',
88 $queryBuilder->expr()->eq('sys_history.sys_log_uid', $queryBuilder->quoteIdentifier('sys_log.uid'))
89 )
90 ->execute()
91 ->fetchAll();
92
93 foreach ($rows as $row) {
94 $logData = $row['log_data'] !== null ? unserialize($row['log_data'], ['allowed_classes' => false]) : [];
95 $updateData = [
96 'actiontype' => RecordHistoryStore::ACTION_MODIFY,
97 'usertype' => 'BE',
98 'userid' => $row['userid'],
99 'sys_log_uid' => 0,
100 'history_data' => json_encode($row['history_data'] !== null ? unserialize($row['history_data'], ['allowed_classes' => false]) : []),
101 'originaluserid' => empty($logData['originalUser']) ? null : $logData['originalUser']
102 ];
103 $connection->update(
104 'sys_history',
105 $updateData,
106 ['uid' => (int)$row['history_uid']],
107 ['uid' => Connection::PARAM_INT]
108 );
109 // Store information about history entry in sys_log table
110 $logData['history'] = $row['history_uid'];
111 $connection->update(
112 'sys_log',
113 ['log_data' => serialize($logData)],
114 ['uid' => (int)$row['uid']],
115 ['uid' => Connection::PARAM_INT]
116 );
117 }
118
119 // Add insert/delete calls
120 $logQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
121 $result = $logQueryBuilder
122 ->select('uid', 'userid', 'action', 'tstamp', 'log_data', 'tablename', 'recuid')
123 ->from('sys_log')
124 ->where(
125 $logQueryBuilder->expr()->eq('type', $logQueryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
126 $logQueryBuilder->expr()->orX(
127 $logQueryBuilder->expr()->eq('action', $logQueryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
128 $logQueryBuilder->expr()->eq('action', $logQueryBuilder->createNamedParameter(3, \PDO::PARAM_INT))
129 )
130 )
131 ->orderBy('uid', 'DESC')
132 ->execute();
133
134 foreach ($result as $row) {
135 $logData = unserialize($row['log_data']);
136 /** @var RecordHistoryStore $store */
137 $store = GeneralUtility::makeInstance(
138 RecordHistoryStore::class,
139 RecordHistoryStore::USER_BACKEND,
140 $row['userid'],
141 (empty($logData['originalUser']) ? null : $logData['originalUser']),
142 $row['tstamp']
143 );
144 switch ($row['action']) {
145 // Insert
146 case 1:
147 $store->addRecord($row['tablename'], $row['recuid'], $logData);
148 break;
149 case 3:
150 // Delete
151 $store->deleteRecord($row['tablename'], $row['recuid']);
152 break;
153 }
154 }
155 $this->markWizardAsDone();
156 return true;
157 }
158
159 /**
160 * Check if given field /column in a table exists
161 *
162 * @param string $table
163 * @param string $fieldName
164 * @return bool
165 */
166 protected function checkIfFieldInTableExists($table, $fieldName)
167 {
168 $tableColumns = GeneralUtility::makeInstance(ConnectionPool::class)
169 ->getConnectionForTable($table)
170 ->getSchemaManager()
171 ->listTableColumns($table);
172
173 return isset($tableColumns[$fieldName]);
174 }
175 }