Merge pull request #18852 from eileenmcnaughton/aip
[civicrm-core.git] / CRM / Logging / Reverter.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Logging_Reverter {
18 private $db;
19 private $log_conn_id;
20 private $log_date;
21
22 /**
23 * The diffs to be reverted.
24 *
25 * @var array
26 */
27 private $diffs = [];
28
29 /**
30 * Class constructor.
31 *
32 * @param string $log_conn_id
33 * @param $log_date
34 */
35 public function __construct($log_conn_id, $log_date) {
36 $dsn = defined('CIVICRM_LOGGING_DSN') ? CRM_Utils_SQL::autoSwitchDSN(CIVICRM_LOGGING_DSN) : CRM_Utils_SQL::autoSwitchDSN(CIVICRM_DSN);
37 $dsn = DB::parseDSN($dsn);
38 $this->db = $dsn['database'];
39 $this->log_conn_id = $log_conn_id;
40 $this->log_date = $log_date;
41 }
42
43 /**
44 *
45 * Calculate a set of diffs based on the connection_id and changes at a close time.
46 *
47 * @param array $tables
48 */
49 public function calculateDiffsFromLogConnAndDate($tables) {
50 $differ = new CRM_Logging_Differ($this->log_conn_id, $this->log_date);
51 $this->diffs = $differ->diffsInTables($tables);
52 }
53
54 /**
55 * Setter for diffs.
56 *
57 * @param array $diffs
58 */
59 public function setDiffs($diffs) {
60 $this->diffs = $diffs;
61 }
62
63 /**
64 * Revert changes in the array of diffs in $this->diffs.
65 */
66 public function revert() {
67
68 // get custom data tables, columns and types
69 $ctypes = [];
70 $dao = CRM_Core_DAO::executeQuery('SELECT table_name, column_name, data_type FROM civicrm_custom_group cg JOIN civicrm_custom_field cf ON (cf.custom_group_id = cg.id)');
71 while ($dao->fetch()) {
72 if (!isset($ctypes[$dao->table_name])) {
73 $ctypes[$dao->table_name] = ['entity_id' => 'Integer'];
74 }
75 $ctypes[$dao->table_name][$dao->column_name] = $dao->data_type;
76 }
77
78 $diffs = $this->diffs;
79 $deletes = [];
80 $reverts = [];
81 foreach ($diffs as $table => $changes) {
82 foreach ($changes as $change) {
83 switch ($change['action']) {
84 case 'Insert':
85 if (!isset($deletes[$table])) {
86 $deletes[$table] = [];
87 }
88 $deletes[$table][] = $change['id'];
89 break;
90
91 case 'Delete':
92 case 'Update':
93 if (!isset($reverts[$table])) {
94 $reverts[$table] = [];
95 }
96 if (!isset($reverts[$table][$change['id']])) {
97 $reverts[$table][$change['id']] = ['log_action' => $change['action']];
98 }
99 $reverts[$table][$change['id']][$change['field']] = $change['from'];
100 break;
101 }
102 }
103 }
104
105 // revert inserts by deleting
106 foreach ($deletes as $table => $ids) {
107 CRM_Core_DAO::executeQuery("DELETE FROM `$table` WHERE id IN (" . implode(', ', array_unique($ids)) . ')');
108 }
109
110 // revert updates by updating to previous values
111 foreach ($reverts as $table => $row) {
112 switch (TRUE) {
113 // DAO-based tables
114
115 case (($tableDAO = CRM_Core_DAO_AllCoreTables::getClassForTable($table)) != FALSE):
116 $dao = new $tableDAO();
117 foreach ($row as $id => $changes) {
118 $dao->id = $id;
119 foreach ($changes as $field => $value) {
120 if ($field == 'log_action') {
121 continue;
122 }
123 if (empty($value) and $value !== 0 and $value !== '0') {
124 $value = 'null';
125 }
126 // Date reaches this point in ISO format (possibly) so strip out stuff
127 // if it does have hyphens of colons demarking the date & it regexes as being a date
128 // or datetime format.
129 if (preg_match('/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/', $value)) {
130 $value = str_replace('-', '', $value);
131 $value = str_replace(':', '', $value);
132 }
133 $dao->$field = $value;
134 }
135 $changes['log_action'] == 'Delete' ? $dao->insert() : $dao->update();
136
137 $dao->reset();
138 }
139 break;
140
141 // custom data tables
142
143 case in_array($table, array_keys($ctypes)):
144 foreach ($row as $id => $changes) {
145 $inserts = ['id' => '%1'];
146 $updates = [];
147 $params = [1 => [$id, 'Integer']];
148 $counter = 2;
149 foreach ($changes as $field => $value) {
150 // don’t try reverting a field that’s no longer there
151 if (!isset($ctypes[$table][$field])) {
152 continue;
153 }
154 $fldVal = "%{$counter}";
155 switch ($ctypes[$table][$field]) {
156 case 'Date':
157 $value = substr(CRM_Utils_Date::isoToMysql($value), 0, 8);
158 break;
159
160 case 'Timestamp':
161 $value = CRM_Utils_Date::isoToMysql($value);
162 break;
163
164 case 'Boolean':
165 if ($value === '') {
166 $fldVal = 'DEFAULT';
167 }
168 }
169 $inserts[$field] = "%$counter";
170 $updates[] = "{$field} = {$fldVal}";
171 if ($fldVal != 'DEFAULT') {
172 $params[$counter] = [$value, $ctypes[$table][$field]];
173 }
174 $counter++;
175 }
176 if ($changes['log_action'] == 'Delete') {
177 $sql = "INSERT INTO `$table` (" . implode(', ', array_keys($inserts)) . ') VALUES (' . implode(', ', $inserts) . ')';
178 }
179 else {
180 $sql = "UPDATE `$table` SET " . implode(', ', $updates) . ' WHERE id = %1';
181 }
182 CRM_Core_DAO::executeQuery($sql, $params);
183 }
184 break;
185 }
186 }
187
188 }
189
190 }