3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Logging_ReportDetail
extends CRM_Report_Form
{
23 * This would be set if we are viewing a merge of 2 contacts.
29 protected $log_conn_id;
32 protected $tables = [];
33 protected $interval = '10 SECOND';
35 protected $altered_name;
36 protected $altered_by;
37 protected $altered_by_id;
40 * detail/summary report ids
49 * @var CRM_Logging_Differ
54 * Array of changes made.
58 protected $diffs = [];
61 * Don't display the Add these contacts to Group button.
65 protected $_add2groupSupported = FALSE;
70 public function __construct() {
74 $this->parsePropertiesFromUrl();
76 parent
::__construct();
78 CRM_Utils_System
::resetBreadCrumb();
81 'title' => ts('Home'),
82 'url' => CRM_Utils_System
::url(),
85 'title' => ts('CiviCRM'),
86 'url' => CRM_Utils_System
::url('civicrm', 'reset=1'),
89 'title' => ts('View Contact'),
90 'url' => CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$this->cid}"),
93 'title' => ts('Search Results'),
94 'url' => CRM_Utils_System
::url('civicrm/contact/search', "force=1"),
97 CRM_Utils_System
::appendBreadCrumb($breadcrumb);
99 if (CRM_Utils_Request
::retrieve('revert', 'Boolean')) {
103 $this->_columnHeaders
= [
104 'field' => ['title' => ts('Field')],
105 'from' => ['title' => ts('Changed From')],
106 'to' => ['title' => ts('Changed To')],
111 * Build query for report.
113 * We override this to be empty & calculate the rows in the buildRows function.
115 * @param bool $applyLimit
117 public function buildQuery($applyLimit = TRUE) {
121 * Build rows from query.
126 public function buildRows($sql, &$rows) {
127 // safeguard for when there aren’t any log entries yet
128 if (!$this->log_conn_id
&& !$this->log_date
) {
132 $rows = $this->convertDiffsToRows();
136 * Get the diffs for the report, calculating them if not already done.
138 * Note that contact details report now uses a more comprehensive method but
139 * the contribution logging details report still uses this.
143 protected function getDiffs() {
144 if (empty($this->diffs
)) {
145 foreach ($this->tables
as $table) {
146 $this->diffs
= array_merge($this->diffs
, $this->diffsInTable($table));
157 protected function diffsInTable($table) {
159 return $this->differ
->diffsInTable($table, $this->cid
);
163 * Convert the diffs to row format.
167 protected function convertDiffsToRows() {
168 // return early if nothing found
169 if (empty($this->diffs
)) {
173 // populate $rows with only the differences between $changed and $original (skipping certain columns and NULL ↔ empty changes unless raw requested)
175 foreach ($this->diffs
as $diff) {
176 $table = $diff['table'];
177 if (empty($metadata[$table])) {
178 list($metadata[$table]['titles'], $metadata[$table]['values']) = $this->differ
->titlesAndValuesForTable($table, $diff['log_date']);
180 $values = CRM_Utils_Array
::value('values', $metadata[$diff['table']], []);
181 $titles = $metadata[$diff['table']]['titles'];
182 $field = $diff['field'];
183 $from = $diff['from'];
187 $field = "$table.$field";
190 if (in_array($field, $skipped)) {
193 // $differ filters out === values; for presentation hide changes like 42 → '42'
198 // special-case for multiple values. Also works for CRM-7251: preferred_communication_method
199 if ((substr($from, 0, 1) == CRM_Core_DAO
::VALUE_SEPARATOR
&&
200 substr($from, -1, 1) == CRM_Core_DAO
::VALUE_SEPARATOR
) ||
201 (substr($to, 0, 1) == CRM_Core_DAO
::VALUE_SEPARATOR
&&
202 substr($to, -1, 1) == CRM_Core_DAO
::VALUE_SEPARATOR
)
205 foreach (explode(CRM_Core_DAO
::VALUE_SEPARATOR
, trim($from, CRM_Core_DAO
::VALUE_SEPARATOR
)) as $val) {
206 $froms[] = $values[$field][$val] ??
NULL;
208 foreach (explode(CRM_Core_DAO
::VALUE_SEPARATOR
, trim($to, CRM_Core_DAO
::VALUE_SEPARATOR
)) as $val) {
209 $tos[] = $values[$field][$val] ??
NULL;
211 $from = implode(', ', array_filter($froms));
212 $to = implode(', ', array_filter($tos));
215 if (isset($values[$field][$from])) {
216 $from = $values[$field][$from];
218 if (isset($values[$field][$to])) {
219 $to = $values[$field][$to];
221 if (isset($titles[$field])) {
222 $field = $titles[$field];
224 if ($diff['action'] == 'Insert') {
227 if ($diff['action'] == 'Delete') {
232 $rows[] = ['field' => $field . " (id: {$diff['id']})", 'from' => $from, 'to' => $to];
238 public function buildQuickForm() {
239 parent
::buildQuickForm();
241 $this->assign('whom_url', CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$this->cid}"));
242 $this->assign('who_url', CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$this->altered_by_id}"));
243 $this->assign('whom_name', $this->altered_name
);
244 $this->assign('who_name', $this->altered_by
);
246 $this->assign('log_date', CRM_Utils_Date
::mysqlToIso($this->log_date
));
248 $q = "reset=1&log_conn_id={$this->log_conn_id}&log_date={$this->log_date}";
250 $q .= '&oid=' . $this->oid
;
252 $this->assign('revertURL', CRM_Report_Utils_Report
::getNextUrl($this->detail
, "$q&revert=1", FALSE, TRUE));
253 $this->assign('revertConfirm', ts('Are you sure you want to revert all changes?'));
257 * Store the dsn for the logging database in $this->db.
259 protected function storeDB() {
260 $dsn = defined('CIVICRM_LOGGING_DSN') ? DB
::parseDSN(CIVICRM_LOGGING_DSN
) : DB
::parseDSN(CIVICRM_DSN
);
261 $this->db
= $dsn['database'];
265 * Calculate all the contact related diffs for the change.
267 protected function calculateContactDiffs() {
268 $this->diffs
= $this->getAllContactChangesForConnection();
272 * Get an array of changes made in the mysql connection.
276 public function getAllContactChangesForConnection() {
277 if (empty($this->log_conn_id
)) {
282 return $this->differ
->getAllChangesForConnection($this->tables
);
284 catch (CRM_Core_Exception
$e) {
285 CRM_Core_Error
::statusBounce(ts($e->getMessage()));
290 * Make sure the differ is defined.
292 protected function setDiffer() {
293 if (empty($this->differ
)) {
294 $this->differ
= new CRM_Logging_Differ($this->log_conn_id
, $this->log_date
, $this->interval
);
299 * Set this tables to reflect tables changed in a merge.
301 protected function setTablesToContactRelatedTables() {
302 $schema = new CRM_Logging_Schema();
303 $this->tables
= $schema->getLogTablesForContact();
304 // allow tables to be extended by report hook query objects.
305 // This is a report specific hook. It's unclear how it interacts to / overlaps the main one.
306 // It probably precedes the main one and was never reconciled with it....
307 CRM_Report_BAO_Hook
::singleton()->alterLogTables($this, $this->tables
);
311 * Revert the changes defined by the parameters.
313 protected function revert() {
314 $reverter = new CRM_Logging_Reverter($this->log_conn_id
, $this->log_date
);
315 $reverter->calculateDiffsFromLogConnAndDate($this->tables
);
317 CRM_Core_Session
::setStatus(ts('The changes have been reverted.'), ts('Reverted'), 'success');
320 CRM_Utils_System
::redirect(CRM_Utils_System
::url(
321 'civicrm/contact/merge',
322 "reset=1&cid={$this->cid}&oid={$this->oid}",
329 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/view', "reset=1&selectedChild=log&cid={$this->cid}", FALSE, NULL, FALSE));
333 CRM_Utils_System
::redirect(CRM_Report_Utils_Report
::getNextUrl($this->summary
, 'reset=1', FALSE, TRUE));
338 * Get the properties that might be in the URL.
340 protected function parsePropertiesFromUrl() {
341 $this->log_conn_id
= CRM_Utils_Request
::retrieve('log_conn_id', 'String');
342 $this->log_date
= CRM_Utils_Request
::retrieve('log_date', 'String');
343 $this->cid
= CRM_Utils_Request
::retrieve('cid', 'Integer');
344 $this->raw
= CRM_Utils_Request
::retrieve('raw', 'Boolean');
346 $this->altered_name
= CRM_Utils_Request
::retrieve('alteredName', 'String');
347 $this->altered_by
= CRM_Utils_Request
::retrieve('alteredBy', 'String');
348 $this->altered_by_id
= CRM_Utils_Request
::retrieve('alteredById', 'Integer');