namespace Civi\Afform;
+use Civi\API\Exception\UnauthorizedException;
use Civi\Api4\Afform;
/**
*/
protected $blocks = [];
+ /**
+ * @var array
+ * Ex: $secureApi4s['spouse'] = function($entity, $action, $params){...};
+ */
+ protected $secureApi4s = [];
+
public function __construct($layout) {
$root = AHQ::makeRoot($layout);
$this->entities = array_column(AHQ::getTags($root, 'af-entity'), NULL, 'name');
$this->parseFields($layout);
}
+ /**
+ * Prepare to access APIv4 on behalf of a particular entity. This will enforce
+ * any security options associated with that entity.
+ *
+ * $formDataModel->getSecureApi4('me')('Contact', 'get', ['where'=>[...]]);
+ * $formDataModel->getSecureApi4('me')('Email', 'create', [...]);
+ *
+ * @param string $entityName
+ * Ex: 'Individual1', 'Individual2', 'me', 'spouse', 'children', 'theMeeting'
+ *
+ * @return callable
+ * API4-style
+ */
+ public function getSecureApi4($entityName) {
+ if (!isset($this->secureApi4s[$entityName])) {
+ if (!isset($this->entities[$entityName])) {
+ throw new UnauthorizedException("Cannot delegate APIv4 calls on behalf of unrecognized entity ($entityName)");
+ }
+ $this->secureApi4s[$entityName] = function(string $entity, string $action, $params = [], $index = NULL) use ($entityName) {
+ // FIXME Pick real value of checkPermissions. Possibly limit by ID.
+ // \Civi::log()->info("secureApi4($entityName): call($entity, $action)");
+ // $params['checkPermissions'] = FALSE;
+ $params['checkPermissions'] = TRUE;
+ return civicrm_api4($entity, $action, $params, $index);
+ };
+ }
+ return $this->secureApi4s[$entityName];
+ }
+
/**
* @param array $nodes
* @param string $entity
public function _run(Result $result) {
// This will throw an exception if the form doesn't exist
$this->_afform = (array) civicrm_api4('Afform', 'get', ['checkPermissions' => FALSE, 'where' => [['name', '=', $this->name]]], 0);
- if ($this->getCheckPermissions()) {
- if (!\CRM_Core_Permission::check("@afform:" . $this->_afform['name'])) {
- throw new \Civi\API\Exception\UnauthorizedException("Authorization failed: Cannot process form " . $this->_afform['name']);
- }
- }
-
$this->_formDataModel = new FormDataModel($this->_afform['layout']);
+ $this->checkPermissions();
$this->validateArgs();
$result->exchangeArray($this->processForm());
}
}
}
+ /**
+ * Assert that the current form submission is authorized.
+ *
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ protected function checkPermissions() {
+ if ($this->getCheckPermissions()) {
+ if (!\CRM_Core_Permission::check("@afform:" . $this->_afform['name'])) {
+ throw new \Civi\API\Exception\UnauthorizedException("Authorization failed: Cannot process form " . $this->_afform['name']);
+ }
+ }
+ }
+
/**
* @return array
*/
* @throws \API_Exception
*/
private function loadEntity($entity, $id) {
- $checkPermissions = TRUE;
- if ($entity['type'] == 'Contact' && !empty($this->args[$entity['name'] . '-cs'])) {
- $checkSum = civicrm_api4('Contact', 'validateChecksum', [
- 'checksum' => $this->args[$entity['name'] . '-cs'],
- 'contactId' => $id,
- ]);
- $checkPermissions = empty($checkSum[0]['valid']);
- }
- $result = civicrm_api4($entity['type'], 'get', [
+ $api4 = $this->_formDataModel->getSecureApi4($entity['name']);
+ $result = $api4($entity['type'], 'get', [
'where' => [['id', '=', $id]],
'select' => array_keys($entity['fields']),
- 'checkPermissions' => $checkPermissions,
]);
foreach ($result as $item) {
$data = ['fields' => $item];
foreach ($entity['joins'] ?? [] as $joinEntity => $join) {
- $data['joins'][$joinEntity] = (array) civicrm_api4($joinEntity, 'get', [
+ $data['joins'][$joinEntity] = (array) $api4($joinEntity, 'get', [
'where' => self::getJoinWhereClause($entity['type'], $joinEntity, $item['id']),
'limit' => !empty($join['af-repeat']) ? $join['max'] ?? 0 : 1,
'select' => array_keys($join['fields']),
- 'checkPermissions' => $checkPermissions,
'orderBy' => self::fieldExists($joinEntity, 'is_primary') ? ['is_primary' => 'DESC'] : [],
]);
}
*/
public static function processContacts(AfformSubmitEvent $event) {
foreach ($event->entityValues['Contact'] ?? [] as $entityName => $contacts) {
+ $api4 = $event->formDataModel->getSecureApi4($entityName);
foreach ($contacts as $contact) {
- $saved = civicrm_api4('Contact', 'save', ['records' => [$contact['fields']]])->first();
- self::saveJoins('Contact', $saved['id'], $contact['joins'] ?? []);
+ $saved = $api4('Contact', 'save', ['records' => [$contact['fields']]])->first();
+ self::saveJoins($api4, 'Contact', $saved['id'], $contact['joins'] ?? []);
}
}
unset($event->entityValues['Contact']);
foreach ($event->entityValues as $entityType => $entities) {
// Each record is an array of one or more items (can be > 1 if af-repeat is used)
foreach ($entities as $entityName => $records) {
+ $api4 = $event->formDataModel->getSecureApi4($entityName);
foreach ($records as $record) {
- $saved = civicrm_api4($entityType, 'save', ['records' => [$record['fields']]])->first();
- self::saveJoins($entityType, $saved['id'], $record['joins'] ?? []);
+ $saved = $api4($entityType, 'save', ['records' => [$record['fields']]])->first();
+ self::saveJoins($api4, $entityType, $saved['id'], $record['joins'] ?? []);
}
}
unset($event->entityValues[$entityType]);
}
}
- protected static function saveJoins($mainEntityName, $entityId, $joins) {
+ protected static function saveJoins($api4, $mainEntityName, $entityId, $joins) {
foreach ($joins as $joinEntityName => $join) {
$values = self::filterEmptyJoins($joinEntityName, $join);
// FIXME: Replace/delete should only be done to known contacts
if ($values) {
- civicrm_api4($joinEntityName, 'replace', [
+ $api4($joinEntityName, 'replace', [
'where' => self::getJoinWhereClause($mainEntityName, $joinEntityName, $entityId),
'records' => $values,
]);
}
else {
try {
- civicrm_api4($joinEntityName, 'delete', [
+ $api4($joinEntityName, 'delete', [
'where' => self::getJoinWhereClause($mainEntityName, $joinEntityName, $entityId),
]);
}