getEntityName()); // 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[$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]); } if ($this->checkPermissions) { foreach ($this->records as $record) { $action = empty($record[$idField]) ? 'create' : 'update'; if (!CoreUtil::checkAccessDelegated($this->getEntityName(), $action, $record, \CRM_Core_Session::getLoggedInContactID() ?: 0)) { throw new UnauthorizedException("ACL check failed"); } } } $e = new ValidateValuesEvent($this, $this->records, new \CRM_Utils_LazyArray(function() use ($idField) { $existingIds = array_column($this->records, $idField); $existing = civicrm_api4($this->getEntityName(), 'get', [ 'checkPermissions' => $this->checkPermissions, 'where' => [[$idField, 'IN', $existingIds]], ], $idField); $result = []; foreach ($this->records as $k => $new) { $old = isset($new[$idField]) ? $existing[$new[$idField]] : NULL; $result[$k] = ['old' => $old, 'new' => $new]; } return $result; })); \Civi::dispatcher()->dispatch('civi.api4.validate', $e); if (!empty($e->errors)) { throw $e->toException(); } } /** * Find existing record based on $this->match param * * @param $record */ protected function matchExisting(&$record) { $primaryKey = CoreUtil::getIdFieldName($this->getEntityName()); if (empty($record[$primaryKey]) && !empty($this->match)) { $where = []; foreach ($record as $key => $val) { if (isset($val) && in_array($key, $this->match, TRUE)) { if ($val === '' || is_null($val)) { // If we want to match empty string we have to match on NULL/'' $where[] = [$key, 'IS EMPTY']; } else { $where[] = [$key, '=', $val]; } } } if (count($where) === count($this->match)) { $existing = civicrm_api4($this->getEntityName(), 'get', [ 'select' => [$primaryKey], 'where' => $where, 'checkPermissions' => $this->checkPermissions, 'limit' => 2, ]); if ($existing->count() === 1) { $record[$primaryKey] = $existing->first()[$primaryKey]; } } } } /** * @return string * @deprecated */ protected function getIdField() { return CoreUtil::getInfoItem($this->getEntityName(), 'primary_key')[0]; } /** * Add one or more records to be saved. * @param array ...$records * @return $this */ public function addRecord(array ...$records) { $this->records = array_merge($this->records, $records); return $this; } /** * Set default value for a field. * @param string $fieldName * @param mixed $defaultValue * @return $this */ public function addDefault(string $fieldName, $defaultValue) { $this->defaults[$fieldName] = $defaultValue; return $this; } /** * Options callback for $this->match * @return array */ protected function getMatchFields() { return (array) civicrm_api4($this->getEntityName(), 'getFields', [ 'checkPermissions' => FALSE, 'action' => 'get', 'where' => [ ['type', 'IN', ['Field', 'Custom']], ['name', 'NOT IN', CoreUtil::getInfoItem($this->getEntityName(), 'primary_key')], ], ], ['name']); } }