Update copyright date for 2020
[civicrm-core.git] / CRM / Logging / ReportDetail.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
f299f7db 31 * @copyright CiviCRM LLC (c) 2004-2020
6a488035
TO
32 */
33class CRM_Logging_ReportDetail extends CRM_Report_Form {
34 protected $cid;
549cd4ca 35
36 /**
37 * Other contact ID.
38 *
39 * This would be set if we are viewing a merge of 2 contacts.
40 *
41 * @var int
42 */
43 protected $oid;
6a488035
TO
44 protected $db;
45 protected $log_conn_id;
46 protected $log_date;
47 protected $raw;
be2fb01f 48 protected $tables = [];
6a488035
TO
49 protected $interval = '10 SECOND';
50
02e02ac5
DS
51 protected $altered_name;
52 protected $altered_by;
53 protected $altered_by_id;
54
971e129b
SL
55 /**
56 * detail/summary report ids
57 * @var int
58 */
6a488035
TO
59 protected $detail;
60 protected $summary;
61
aa00132e 62 /**
63 * Instance of Differ.
64 *
65 * @var CRM_Logging_Differ
66 */
67 protected $differ;
68
69 /**
70 * Array of changes made.
71 *
72 * @var array
73 */
be2fb01f 74 protected $diffs = [];
aa00132e 75
e0ef6999 76 /**
e480ef09 77 * Don't display the Add these contacts to Group button.
78 *
79 * @var bool
80 */
81 protected $_add2groupSupported = FALSE;
82
83 /**
84 * Class constructor.
e0ef6999 85 */
00be9182 86 public function __construct() {
6a488035 87
e480ef09 88 $this->storeDB();
6a488035 89
3b45d110 90 $this->parsePropertiesFromUrl();
e0ef6999 91
6a488035
TO
92 parent::__construct();
93
94 CRM_Utils_System::resetBreadCrumb();
be2fb01f
CW
95 $breadcrumb = [
96 [
bed98343 97 'title' => ts('Home'),
98 'url' => CRM_Utils_System::url(),
be2fb01f
CW
99 ],
100 [
bed98343 101 'title' => ts('CiviCRM'),
102 'url' => CRM_Utils_System::url('civicrm', 'reset=1'),
be2fb01f
CW
103 ],
104 [
bed98343 105 'title' => ts('View Contact'),
106 'url' => CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->cid}"),
be2fb01f
CW
107 ],
108 [
bed98343 109 'title' => ts('Search Results'),
110 'url' => CRM_Utils_System::url('civicrm/contact/search', "force=1"),
be2fb01f
CW
111 ],
112 ];
6a488035
TO
113 CRM_Utils_System::appendBreadCrumb($breadcrumb);
114
e480ef09 115 if (CRM_Utils_Request::retrieve('revert', 'Boolean')) {
116 $this->revert();
6a488035
TO
117 }
118
be2fb01f
CW
119 $this->_columnHeaders = [
120 'field' => ['title' => ts('Field')],
121 'from' => ['title' => ts('Changed From')],
122 'to' => ['title' => ts('Changed To')],
123 ];
6a488035
TO
124 }
125
e0ef6999 126 /**
aa00132e 127 * Build query for report.
128 *
129 * We override this to be empty & calculate the rows in the buildRows function.
130 *
e0ef6999
EM
131 * @param bool $applyLimit
132 */
6ea503d4
TO
133 public function buildQuery($applyLimit = TRUE) {
134 }
6a488035 135
e0ef6999 136 /**
549cd4ca 137 * Build rows from query.
138 *
139 * @param string $sql
140 * @param array $rows
e0ef6999 141 */
00be9182 142 public function buildRows($sql, &$rows) {
6a488035 143 // safeguard for when there aren’t any log entries yet
10b32ed4 144 if (!$this->log_conn_id && !$this->log_date) {
6a488035
TO
145 return;
146 }
aa00132e 147 $this->getDiffs();
148 $rows = $this->convertDiffsToRows();
149 }
6a488035 150
aa00132e 151 /**
152 * Get the diffs for the report, calculating them if not already done.
153 *
154 * Note that contact details report now uses a more comprehensive method but
155 * the contribution logging details report still uses this.
156 *
157 * @return array
158 */
159 protected function getDiffs() {
160 if (empty($this->diffs)) {
161 foreach ($this->tables as $table) {
162 $this->diffs = array_merge($this->diffs, $this->diffsInTable($table));
163 }
6a488035 164 }
aa00132e 165 return $this->diffs;
6a488035
TO
166 }
167
e0ef6999
EM
168 /**
169 * @param $table
170 *
171 * @return array
172 */
6a488035 173 protected function diffsInTable($table) {
aa00132e 174 $this->setDiffer();
175 return $this->differ->diffsInTable($table, $this->cid);
176 }
6a488035 177
aa00132e 178 /**
179 * Convert the diffs to row format.
180 *
181 * @return array
182 */
183 protected function convertDiffsToRows() {
6a488035 184 // return early if nothing found
aa00132e 185 if (empty($this->diffs)) {
be2fb01f 186 return [];
6a488035
TO
187 }
188
6a488035 189 // populate $rows with only the differences between $changed and $original (skipping certain columns and NULL ↔ empty changes unless raw requested)
be2fb01f 190 $skipped = ['id'];
aa00132e 191 foreach ($this->diffs as $diff) {
192 $table = $diff['table'];
193 if (empty($metadata[$table])) {
10b32ed4 194 list($metadata[$table]['titles'], $metadata[$table]['values']) = $this->differ->titlesAndValuesForTable($table, $diff['log_date']);
aa00132e 195 }
be2fb01f 196 $values = CRM_Utils_Array::value('values', $metadata[$diff['table']], []);
aa00132e 197 $titles = $metadata[$diff['table']]['titles'];
6a488035 198 $field = $diff['field'];
353ffa53
TO
199 $from = $diff['from'];
200 $to = $diff['to'];
6a488035
TO
201
202 if ($this->raw) {
203 $field = "$table.$field";
204 }
205 else {
206 if (in_array($field, $skipped)) {
207 continue;
208 }
209 // $differ filters out === values; for presentation hide changes like 42 → '42'
210 if ($from == $to) {
211 continue;
212 }
213
40554aa3 214 // special-case for multiple values. Also works for CRM-7251: preferred_communication_method
e0ef6999
EM
215 if ((substr($from, 0, 1) == CRM_Core_DAO::VALUE_SEPARATOR &&
216 substr($from, -1, 1) == CRM_Core_DAO::VALUE_SEPARATOR) ||
217 (substr($to, 0, 1) == CRM_Core_DAO::VALUE_SEPARATOR &&
353ffa53
TO
218 substr($to, -1, 1) == CRM_Core_DAO::VALUE_SEPARATOR)
219 ) {
be2fb01f 220 $froms = $tos = [];
40554aa3
DS
221 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($from, CRM_Core_DAO::VALUE_SEPARATOR)) as $val) {
222 $froms[] = CRM_Utils_Array::value($val, $values[$field]);
223 }
224 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($to, CRM_Core_DAO::VALUE_SEPARATOR)) as $val) {
225 $tos[] = CRM_Utils_Array::value($val, $values[$field]);
226 }
6a488035 227 $from = implode(', ', array_filter($froms));
353ffa53 228 $to = implode(', ', array_filter($tos));
6a488035
TO
229 }
230
231 if (isset($values[$field][$from])) {
6a488035 232 $from = $values[$field][$from];
6a488035
TO
233 }
234 if (isset($values[$field][$to])) {
235 $to = $values[$field][$to];
236 }
237 if (isset($titles[$field])) {
238 $field = $titles[$field];
239 }
240 if ($diff['action'] == 'Insert') {
241 $from = '';
242 }
243 if ($diff['action'] == 'Delete') {
244 $to = '';
245 }
246 }
247
be2fb01f 248 $rows[] = ['field' => $field . " (id: {$diff['id']})", 'from' => $from, 'to' => $to];
6a488035
TO
249 }
250
251 return $rows;
252 }
253
00be9182 254 public function buildQuickForm() {
6a488035
TO
255 parent::buildQuickForm();
256
02e02ac5 257 $this->assign('whom_url', CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->cid}"));
353ffa53 258 $this->assign('who_url', CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->altered_by_id}"));
02e02ac5 259 $this->assign('whom_name', $this->altered_name);
353ffa53 260 $this->assign('who_name', $this->altered_by);
02e02ac5 261
6a488035
TO
262 $this->assign('log_date', CRM_Utils_Date::mysqlToIso($this->log_date));
263
264 $q = "reset=1&log_conn_id={$this->log_conn_id}&log_date={$this->log_date}";
549cd4ca 265 if ($this->oid) {
266 $q .= '&oid=' . $this->oid;
267 }
6a488035 268 $this->assign('revertURL', CRM_Report_Utils_Report::getNextUrl($this->detail, "$q&revert=1", FALSE, TRUE));
7266e09b 269 $this->assign('revertConfirm', ts('Are you sure you want to revert all changes?'));
6a488035 270 }
96025800 271
e480ef09 272 /**
273 * Store the dsn for the logging database in $this->db.
274 */
275 protected function storeDB() {
276 $dsn = defined('CIVICRM_LOGGING_DSN') ? DB::parseDSN(CIVICRM_LOGGING_DSN) : DB::parseDSN(CIVICRM_DSN);
277 $this->db = $dsn['database'];
278 }
279
aa00132e 280 /**
281 * Calculate all the contact related diffs for the change.
aa00132e 282 */
549cd4ca 283 protected function calculateContactDiffs() {
aa00132e 284 $this->diffs = $this->getAllContactChangesForConnection();
285 }
286
aa00132e 287 /**
288 * Get an array of changes made in the mysql connection.
289 *
290 * @return mixed
291 */
292 public function getAllContactChangesForConnection() {
293 if (empty($this->log_conn_id)) {
be2fb01f 294 return [];
aa00132e 295 }
296 $this->setDiffer();
297 try {
298 return $this->differ->getAllChangesForConnection($this->tables);
299 }
300 catch (CRM_Core_Exception $e) {
301 CRM_Core_Error::statusBounce(ts($e->getMessage()));
302 }
303 }
304
305 /**
306 * Make sure the differ is defined.
307 */
308 protected function setDiffer() {
309 if (empty($this->differ)) {
310 $this->differ = new CRM_Logging_Differ($this->log_conn_id, $this->log_date, $this->interval);
311 }
312 }
313
314 /**
315 * Set this tables to reflect tables changed in a merge.
316 */
317 protected function setTablesToContactRelatedTables() {
318 $schema = new CRM_Logging_Schema();
319 $this->tables = $schema->getLogTablesForContact();
320 // allow tables to be extended by report hook query objects.
321 // This is a report specific hook. It's unclear how it interacts to / overlaps the main one.
322 // It probably precedes the main one and was never reconciled with it....
323 CRM_Report_BAO_Hook::singleton()->alterLogTables($this, $this->tables);
324 }
325
e480ef09 326 /**
327 * Revert the changes defined by the parameters.
328 */
329 protected function revert() {
330 $reverter = new CRM_Logging_Reverter($this->log_conn_id, $this->log_date);
93afbc3a 331 $reverter->calculateDiffsFromLogConnAndDate($this->tables);
332 $reverter->revert();
e480ef09 333 CRM_Core_Session::setStatus(ts('The changes have been reverted.'), ts('Reverted'), 'success');
334 if ($this->cid) {
549cd4ca 335 if ($this->oid) {
336 CRM_Utils_System::redirect(CRM_Utils_System::url(
337 'civicrm/contact/merge',
338 "reset=1&cid={$this->cid}&oid={$this->oid}",
339 FALSE,
340 NULL,
341 FALSE)
342 );
343 }
344 else {
345 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/view', "reset=1&selectedChild=log&cid={$this->cid}", FALSE, NULL, FALSE));
346 }
e480ef09 347 }
348 else {
349 CRM_Utils_System::redirect(CRM_Report_Utils_Report::getNextUrl($this->summary, 'reset=1', FALSE, TRUE));
350 }
351 }
352
3b45d110 353 /**
354 * Get the properties that might be in the URL.
355 */
356 protected function parsePropertiesFromUrl() {
a3d827a7
CW
357 $this->log_conn_id = CRM_Utils_Request::retrieve('log_conn_id', 'String');
358 $this->log_date = CRM_Utils_Request::retrieve('log_date', 'String');
359 $this->cid = CRM_Utils_Request::retrieve('cid', 'Integer');
360 $this->raw = CRM_Utils_Request::retrieve('raw', 'Boolean');
361
362 $this->altered_name = CRM_Utils_Request::retrieve('alteredName', 'String');
363 $this->altered_by = CRM_Utils_Request::retrieve('alteredBy', 'String');
364 $this->altered_by_id = CRM_Utils_Request::retrieve('alteredById', 'Integer');
3b45d110 365 }
366
6a488035 367}