From 9f04ea33de122a5087450444fa8ffcea55aeab12 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 19 May 2021 22:11:29 -0700 Subject: [PATCH] ValidateValues - Provide optional access to complete records --- Civi/Api4/Event/ValidateValuesEvent.php | 23 +++++++++++++++++++++- Civi/Api4/Generic/AbstractCreateAction.php | 4 +++- Civi/Api4/Generic/AbstractSaveAction.php | 15 +++++++++++++- Civi/Api4/Generic/AbstractUpdateAction.php | 9 ++++++++- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Civi/Api4/Event/ValidateValuesEvent.php b/Civi/Api4/Event/ValidateValuesEvent.php index 146f29f2cb..6444ad3b67 100644 --- a/Civi/Api4/Event/ValidateValuesEvent.php +++ b/Civi/Api4/Event/ValidateValuesEvent.php @@ -70,6 +70,24 @@ class ValidateValuesEvent extends GenericHookEvent { */ public $records; + /** + * Detailed, side-by-side comparison of old and new values. + * + * This requires loading the list of old values from the database. Consequently, + * reading `$diffs` is more expensive than reading `$records`, so you should only use it if + * really necessary. + * + * The list of $diffs may be important if you are enforcing a rule that involves + * multiple fields. (Ex: "Validate that the state_id and country_id match.") + * + * When possible, $records and $diffs will have the same number of items (with corresponding + * keys). However, in the case of a batch `update()`, the list of diffs will be longer. + * + * @var array|\CRM_Utils_LazyArray + * Each item is a record of the form ['old' => $fieldValues, 'new' => $fieldValues] + */ + public $diffs; + /** * List of error messages. * @@ -85,10 +103,13 @@ class ValidateValuesEvent extends GenericHookEvent { * @param \Civi\Api4\Generic\AbstractAction $apiRequest * @param array|\CRM_Utils_LazyArray $records * List of updates (akin to SaveAction::$records). + * @param array|\CRM_Utils_LazyArray $diffs + * List of differences (comparing old values and new values). */ - public function __construct($apiRequest, $records) { + public function __construct($apiRequest, $records, $diffs) { $this->setApiRequest($apiRequest); $this->records = $records; + $this->diffs = $diffs; $this->errors = []; } diff --git a/Civi/Api4/Generic/AbstractCreateAction.php b/Civi/Api4/Generic/AbstractCreateAction.php index 5299741a8f..153e26ce6f 100644 --- a/Civi/Api4/Generic/AbstractCreateAction.php +++ b/Civi/Api4/Generic/AbstractCreateAction.php @@ -66,7 +66,9 @@ abstract class AbstractCreateAction extends AbstractAction { if ($unmatched) { throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); } - $e = new ValidateValuesEvent($this, [$this->getValues()]); + $e = new ValidateValuesEvent($this, [$this->getValues()], new \CRM_Utils_LazyArray(function () { + return [['old' => NULL, 'new' => $this->getValues()]]; + })); \Civi::dispatcher()->dispatch('civi.api4.validate', $e); if (!empty($e->errors)) { throw $e->toException(); diff --git a/Civi/Api4/Generic/AbstractSaveAction.php b/Civi/Api4/Generic/AbstractSaveAction.php index d67fa3c2c5..29f329cc66 100644 --- a/Civi/Api4/Generic/AbstractSaveAction.php +++ b/Civi/Api4/Generic/AbstractSaveAction.php @@ -105,7 +105,20 @@ abstract class AbstractSaveAction extends AbstractAction { if ($unmatched) { throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); } - $e = new ValidateValuesEvent($this, $this->records); + $e = new ValidateValuesEvent($this, $this->records, new \CRM_Utils_LazyArray(function() { + $existingIds = array_column($this->records, $this->idField); + $existing = civicrm_api4($this->getEntityName(), 'get', [ + 'checkPermissions' => $this->checkPermissions, + 'where' => [[$this->idField, 'IN', $existingIds]], + ], $this->idField); + + $result = []; + foreach ($this->records as $k => $new) { + $old = isset($new[$this->idField]) ? $existing[$new[$this->idField]] : NULL; + $result[$k] = ['old' => $old, 'new' => $new]; + } + return $result; + })); \Civi::dispatcher()->dispatch('civi.api4.validate', $e); if (!empty($e->errors)) { throw $e->toException(); diff --git a/Civi/Api4/Generic/AbstractUpdateAction.php b/Civi/Api4/Generic/AbstractUpdateAction.php index f4fb2ec8f8..42a61cccf7 100644 --- a/Civi/Api4/Generic/AbstractUpdateAction.php +++ b/Civi/Api4/Generic/AbstractUpdateAction.php @@ -77,7 +77,14 @@ abstract class AbstractUpdateAction extends AbstractBatchAction { */ protected function validateValues() { // FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception? - $e = new ValidateValuesEvent($this, [$this->values]); + $e = new ValidateValuesEvent($this, [$this->values], new \CRM_Utils_LazyArray(function () { + $existing = $this->getBatchAction()->setSelect(['*'])->execute(); + $result = []; + foreach ($existing as $record) { + $result[] = ['old' => $record, 'new' => $this->values]; + } + return $result; + })); \Civi::dispatcher()->dispatch('civi.api4.validate', $e); if (!empty($e->errors)) { throw $e->toException(); -- 2.25.1