Merge pull request #23707 from eileenmcnaughton/import_activity
[civicrm-core.git] / CRM / Logging / ReportDetail.php
index fdbd92216a895882e7e64861f067e6119585fb40..e026f3d4adb03b611967cafd4626487983ff1a47 100644 (file)
@@ -15,6 +15,8 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 class CRM_Logging_ReportDetail extends CRM_Report_Form {
+
+  const ROW_COUNT_LIMIT = 50;
   protected $cid;
 
   /**
@@ -101,9 +103,9 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
     }
 
     $this->_columnHeaders = [
-      'field' => ['title' => ts('Field')],
-      'from' => ['title' => ts('Changed From')],
-      'to' => ['title' => ts('Changed To')],
+      'field' => ['title' => ts('Field'), 'type' => CRM_Utils_Type::T_STRING],
+      'from' => ['title' => ts('Changed From'), 'type' => CRM_Utils_Type::T_STRING],
+      'to' => ['title' => ts('Changed To'), 'type' => CRM_Utils_Type::T_STRING],
     ];
   }
 
@@ -172,6 +174,7 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
 
     // populate $rows with only the differences between $changed and $original (skipping certain columns and NULL ↔ empty changes unless raw requested)
     $skipped = ['id'];
+    $nRows = $rows = [];
     foreach ($this->diffs as $diff) {
       $table = $diff['table'];
       if (empty($metadata[$table])) {
@@ -212,12 +215,25 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
           $to = implode(', ', array_filter($tos));
         }
 
+        $tableDAOClass = CRM_Core_DAO_AllCoreTables::getClassForTable($table);
+        if (!empty($tableDAOClass)) {
+          $tableDAOFields = (new $tableDAOClass())->fields();
+          // If this field is a foreign key, then we can later use the foreign
+          // class to translate the id into something more useful for display.
+          $fkClassName = $tableDAOFields[$field]['FKClassName'] ?? NULL;
+        }
         if (isset($values[$field][$from])) {
           $from = $values[$field][$from];
         }
+        elseif (!empty($from) && !empty($fkClassName)) {
+          $from = $this->convertForeignKeyValuesToLabels($fkClassName, $field, $from);
+        }
         if (isset($values[$field][$to])) {
           $to = $values[$field][$to];
         }
+        elseif (!empty($to) && !empty($fkClassName)) {
+          $to = $this->convertForeignKeyValuesToLabels($fkClassName, $field, $to);
+        }
         if (isset($titles[$field])) {
           $field = $titles[$field];
         }
@@ -228,10 +244,31 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
           $to = '';
         }
       }
-
-      $rows[] = ['field' => $field . " (id: {$diff['id']})", 'from' => $from, 'to' => $to];
+      // Rework the results to provide grouping based on the ID
+      // We don't need that field displayed so we will output empty
+      if ($field == 'Modified Date') {
+        $nRows[$diff['id']][] = ['field' => '', 'from' => $from, 'to' => $to];
+      }
+      else {
+        $nRows[$diff['id']][] = ['field' => $field . " (id: {$diff['id']})", 'from' => $from, 'to' => $to];
+      }
     }
+    // Transform the output so that we can compact the changes into the proper amount of rows IF trData is holding more than 1 array
+    foreach ($nRows as $trData) {
+      if (count($trData) > 1) {
+        $keys = array_intersect(...array_map('array_keys', $trData));
+        $mergedRes = array_combine($keys, array_map(function ($key) use ($trData) {
+          // If more than 1 entry is found, we are assigning them as subarrays, then the tpls will be responsible for concatenating the results
+          return array_column($trData, $key);
+        }, $keys));
+        $rows[] = $mergedRes;
+      }
+      else {
+        // We always need the first row of that array
+        $rows[] = $trData[0];
+      }
 
+    }
     return $rows;
   }
 
@@ -251,6 +288,7 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
     }
     $this->assign('revertURL', CRM_Report_Utils_Report::getNextUrl($this->detail, "$q&revert=1", FALSE, TRUE));
     $this->assign('revertConfirm', ts('Are you sure you want to revert all changes?'));
+    $this->assign('sections', []);
   }
 
   /**
@@ -266,6 +304,9 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
    * Calculate all the contact related diffs for the change.
    */
   protected function calculateContactDiffs() {
+    $this->_rowsFound = $this->getCountOfAllContactChangesForConnection();
+    // Apply some limits before asking for all contact changes
+    $this->getLimit();
     $this->diffs = $this->getAllContactChangesForConnection();
   }
 
@@ -280,10 +321,28 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
     }
     $this->setDiffer();
     try {
-      return $this->differ->getAllChangesForConnection($this->tables);
+      return $this->differ->getAllChangesForConnection($this->tables, $this->dblimit, $this->dboffset);
     }
     catch (CRM_Core_Exception $e) {
-      CRM_Core_Error::statusBounce(ts($e->getMessage()));
+      CRM_Core_Error::statusBounce($e->getMessage());
+    }
+  }
+
+  /**
+   * Get an count of contacts with changes.
+   *
+   * @return mixed
+   */
+  public function getCountOfAllContactChangesForConnection() {
+    if (empty($this->log_conn_id)) {
+      return [];
+    }
+    $this->setDiffer();
+    try {
+      return $this->differ->getCountOfAllContactChangesForConnection($this->tables);
+    }
+    catch (CRM_Core_Exception $e) {
+      CRM_Core_Error::statusBounce($e->getMessage());
     }
   }
 
@@ -347,6 +406,83 @@ class CRM_Logging_ReportDetail extends CRM_Report_Form {
     $this->altered_name = CRM_Utils_Request::retrieve('alteredName', 'String');
     $this->altered_by = CRM_Utils_Request::retrieve('alteredBy', 'String');
     $this->altered_by_id = CRM_Utils_Request::retrieve('alteredById', 'Integer');
+    $this->layout = CRM_Utils_Request::retrieve('layout', 'String');
+  }
+
+  /**
+   * Override to set limit
+   * @param int $rowCount
+   */
+  public function limit($rowCount = self::ROW_COUNT_LIMIT) {
+    parent::limit($rowCount);
+  }
+
+  /**
+   * Override to set pager with limit
+   * @param int $rowCount
+   */
+  public function setPager($rowCount = self::ROW_COUNT_LIMIT) {
+    // We should not be rendering the pager in overlay mode
+    if (!isset($this->layout)) {
+      $this->_dashBoardRowCount = $rowCount;
+      $this->_limit = TRUE;
+      parent::setPager($rowCount);
+    }
+  }
+
+  /**
+   * This is a function similar to limit, in fact we copied it as-is and removed
+   * some `set` statements
+   *
+   */
+  public function getLimit($rowCount = self::ROW_COUNT_LIMIT) {
+    if ($this->addPaging) {
+
+      $pageId = CRM_Utils_Request::retrieve('crmPID', 'Integer');
+
+      // @todo all http vars should be extracted in the preProcess
+      // - not randomly in the class
+      if (!$pageId && !empty($_POST)) {
+        if (isset($_POST['PagerBottomButton']) && isset($_POST['crmPID_B'])) {
+          $pageId = max((int) $_POST['crmPID_B'], 1);
+        }
+        elseif (isset($_POST['PagerTopButton']) && isset($_POST['crmPID'])) {
+          $pageId = max((int) $_POST['crmPID'], 1);
+        }
+        unset($_POST['crmPID_B'], $_POST['crmPID']);
+      }
+
+      $pageId = $pageId ? $pageId : 1;
+      $offset = ($pageId - 1) * $rowCount;
+
+      $offset = CRM_Utils_Type::escape($offset, 'Int');
+      $rowCount = CRM_Utils_Type::escape($rowCount, 'Int');
+      $this->_limit = " LIMIT $offset, $rowCount";
+      $this->dblimit = $rowCount;
+      $this->dboffset = $offset;
+    }
+  }
+
+  /**
+   * Given a key value that we know is a foreign key to another table, return
+   * what the DAO thinks is the "label" for the foreign entity. For example
+   * if it's referencing a contact then return the contact name, or if it's an
+   * activity then return the activity subject.
+   * If it's the type of DAO that doesn't have such a thing, just echo back
+   * what we were given.
+   *
+   * @param string $fkClassName
+   * @param string $field
+   * @param int $keyval
+   * @return string
+   */
+  private function convertForeignKeyValuesToLabels(string $fkClassName, string $field, int $keyval): string {
+    if ($fkClassName::$_labelField) {
+      $labelValue = CRM_Core_DAO::getFieldValue($fkClassName, $keyval, $fkClassName::$_labelField);
+      // Not sure if this should use ts - there's not a lot of context (`%1 (id: %2)`) - and also the similar field labels above don't use ts.
+      return "{$labelValue} (id: {$keyval})";
+    }
+    return (string) $keyval;
   }
 
 }