diff --git a/Civi/Api4/Generic/AbstractAction.php b/Civi/Api4/Generic/AbstractAction.php index 050fd5a6ca43..0957b696b1a7 100644 --- a/Civi/Api4/Generic/AbstractAction.php +++ b/Civi/Api4/Generic/AbstractAction.php @@ -456,30 +456,6 @@ public function reflect() { return $this->_reflection; } - /** - * Validates required fields for actions which create a new object. - * - * @param $values - * @return array - * @throws \API_Exception - */ - protected function checkRequiredFields($values) { - $unmatched = []; - foreach ($this->entityFields() as $fieldName => $fieldInfo) { - if (!isset($values[$fieldName]) || $values[$fieldName] === '') { - if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) { - $unmatched[] = $fieldName; - } - elseif (!empty($fieldInfo['required_if'])) { - if ($this->evaluateCondition($fieldInfo['required_if'], ['values' => $values])) { - $unmatched[] = $fieldName; - } - } - } - } - return $unmatched; - } - /** * Replaces pseudoconstants in input values * diff --git a/Civi/Api4/Generic/AbstractCreateAction.php b/Civi/Api4/Generic/AbstractCreateAction.php index 153e26ce6f59..71f63613ed65 100644 --- a/Civi/Api4/Generic/AbstractCreateAction.php +++ b/Civi/Api4/Generic/AbstractCreateAction.php @@ -31,6 +31,8 @@ */ abstract class AbstractCreateAction extends AbstractAction { + use \Civi\Api4\Generic\Traits\CheckRequiredTrait; + /** * Field values to set for the new $ENTITY. * @@ -61,11 +63,7 @@ public function addValue(string $fieldName, $value) { * @throws \API_Exception */ protected function validateValues() { - // FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception? - $unmatched = $this->checkRequiredFields($this->getValues()); - if ($unmatched) { - throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); - } + $this->checkRequired([$this->values]); $e = new ValidateValuesEvent($this, [$this->getValues()], new \CRM_Utils_LazyArray(function () { return [['old' => NULL, 'new' => $this->getValues()]]; })); diff --git a/Civi/Api4/Generic/AbstractSaveAction.php b/Civi/Api4/Generic/AbstractSaveAction.php index 29f329cc661e..3a5998888537 100644 --- a/Civi/Api4/Generic/AbstractSaveAction.php +++ b/Civi/Api4/Generic/AbstractSaveAction.php @@ -42,6 +42,8 @@ */ abstract class AbstractSaveAction extends AbstractAction { + use \Civi\Api4\Generic\Traits\CheckRequiredTrait; + /** * Array of $ENTITIES to save. * @@ -95,16 +97,10 @@ public function __construct($entityName, $actionName, $idField = 'id') { * @throws \API_Exception */ protected function validateValues() { - // FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception? - $unmatched = []; - foreach ($this->records as $record) { - if (empty($record[$this->idField])) { - $unmatched = array_unique(array_merge($unmatched, $this->checkRequiredFields($record))); - } - } - if ($unmatched) { - throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); - } + $this->checkRequired($this->records, function($record) { + return empty($record[$this->idField]); + }); + $e = new ValidateValuesEvent($this, $this->records, new \CRM_Utils_LazyArray(function() { $existingIds = array_column($this->records, $this->idField); $existing = civicrm_api4($this->getEntityName(), 'get', [ diff --git a/Civi/Api4/Generic/Traits/CheckRequiredTrait.php b/Civi/Api4/Generic/Traits/CheckRequiredTrait.php new file mode 100644 index 000000000000..ded6f7940a8f --- /dev/null +++ b/Civi/Api4/Generic/Traits/CheckRequiredTrait.php @@ -0,0 +1,68 @@ +$idField), and it can't + * be fully extracted until those are changed. + * + * @deprecated + */ +trait CheckRequiredTrait { + + abstract protected static function getEntityName(); + + abstract protected static function getActionName(); + + abstract protected function entityFields(); + + abstract protected function evaluateCondition($expr, $vars); + + /** + * When creating new records, ensure that the 'required' fields are present. Throws an exception if any fields are missing. + * + * @param array $records + * @param callable|TRUE $isNew A function that can distinguish whether the record is new, or constant TRUE if all records are new. + * @throws \API_Exception + */ + protected function checkRequired($records, $isNew = TRUE) { + $unmatched = []; + + foreach ($records as $record) { + if ($isNew === TRUE || $isNew($record)) { + $unmatched = array_unique(array_merge($unmatched, $this->checkRequiredFieldValues($record))); + } + } + if ($unmatched) { + throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); + } + } + + /** + * Validates required fields for actions which create a new object. + * + * @param $values + * @return array + * @throws \API_Exception + */ + protected function checkRequiredFieldValues($values) { + $unmatched = []; + foreach ($this->entityFields() as $fieldName => $fieldInfo) { + if (!isset($values[$fieldName]) || $values[$fieldName] === '') { + if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) { + $unmatched[] = $fieldName; + } + elseif (!empty($fieldInfo['required_if'])) { + if ($this->evaluateCondition($fieldInfo['required_if'], ['values' => $values])) { + $unmatched[] = $fieldName; + } + } + } + } + return $unmatched; + } + +}