Afform - Delegated API calls should use a security helper
[civicrm-core.git] / ext / afform / core / Civi / Afform / FormDataModel.php
CommitLineData
fc54326f
TO
1<?php
2
3namespace Civi\Afform;
4
0fd2d6af 5use Civi\API\Exception\UnauthorizedException;
344e8290
CW
6use Civi\Api4\Afform;
7
fc54326f
TO
8/**
9 * Class FormDataModel
10 * @package Civi\Afform
11 *
344e8290 12 * Examines a form and determines the entities, fields & joins in use.
fc54326f
TO
13 */
14class FormDataModel {
15
16 /**
17 * @var array
18 * Ex: $entities['spouse']['type'] = 'Contact';
19 */
20 protected $entities;
21
22 /**
344e8290 23 * @var array
fc54326f 24 */
344e8290
CW
25 protected $blocks = [];
26
0fd2d6af
TO
27 /**
28 * @var array
29 * Ex: $secureApi4s['spouse'] = function($entity, $action, $params){...};
30 */
31 protected $secureApi4s = [];
32
344e8290 33 public function __construct($layout) {
fb96a2d7 34 $root = AHQ::makeRoot($layout);
344e8290 35 $this->entities = array_column(AHQ::getTags($root, 'af-entity'), NULL, 'name');
344e8290
CW
36 foreach (array_keys($this->entities) as $entity) {
37 $this->entities[$entity]['fields'] = $this->entities[$entity]['joins'] = [];
38 }
e38db494
CW
39 // Pre-load full list of afforms in case this layout embeds other afform directives
40 $this->blocks = (array) Afform::get()->setCheckPermissions(FALSE)->setSelect(['name', 'directive_name'])->execute()->indexBy('directive_name');
344e8290 41 $this->parseFields($layout);
fc54326f
TO
42 }
43
0fd2d6af
TO
44 /**
45 * Prepare to access APIv4 on behalf of a particular entity. This will enforce
46 * any security options associated with that entity.
47 *
48 * $formDataModel->getSecureApi4('me')('Contact', 'get', ['where'=>[...]]);
49 * $formDataModel->getSecureApi4('me')('Email', 'create', [...]);
50 *
51 * @param string $entityName
52 * Ex: 'Individual1', 'Individual2', 'me', 'spouse', 'children', 'theMeeting'
53 *
54 * @return callable
55 * API4-style
56 */
57 public function getSecureApi4($entityName) {
58 if (!isset($this->secureApi4s[$entityName])) {
59 if (!isset($this->entities[$entityName])) {
60 throw new UnauthorizedException("Cannot delegate APIv4 calls on behalf of unrecognized entity ($entityName)");
61 }
62 $this->secureApi4s[$entityName] = function(string $entity, string $action, $params = [], $index = NULL) use ($entityName) {
63 // FIXME Pick real value of checkPermissions. Possibly limit by ID.
64 // \Civi::log()->info("secureApi4($entityName): call($entity, $action)");
65 // $params['checkPermissions'] = FALSE;
66 $params['checkPermissions'] = TRUE;
67 return civicrm_api4($entity, $action, $params, $index);
68 };
69 }
70 return $this->secureApi4s[$entityName];
71 }
72
fc54326f 73 /**
e1aca853 74 * @param array $nodes
e1aca853 75 * @param string $entity
344e8290 76 * @param string $join
fc54326f 77 */
344e8290 78 protected function parseFields($nodes, $entity = NULL, $join = NULL) {
e1aca853
CW
79 foreach ($nodes as $node) {
80 if (!is_array($node) || !isset($node['#tag'])) {
344e8290 81 continue;
fa5d6183 82 }
344e8290
CW
83 elseif (!empty($node['af-fieldset']) && !empty($node['#children'])) {
84 $this->parseFields($node['#children'], $node['af-fieldset'], $join);
e1aca853
CW
85 }
86 elseif ($entity && $node['#tag'] === 'af-field') {
344e8290
CW
87 if ($join) {
88 $this->entities[$entity]['joins'][$join]['fields'][$node['name']] = AHQ::getProps($node);
89 }
90 else {
91 $this->entities[$entity]['fields'][$node['name']] = AHQ::getProps($node);
92 }
e1aca853 93 }
344e8290
CW
94 elseif ($entity && !empty($node['af-join'])) {
95 $this->entities[$entity]['joins'][$node['af-join']] = AHQ::getProps($node);
96 $this->parseFields($node['#children'] ?? [], $entity, $node['af-join']);
fc54326f 97 }
e1aca853 98 elseif (!empty($node['#children'])) {
344e8290
CW
99 $this->parseFields($node['#children'], $entity, $join);
100 }
101 // Recurse into embedded blocks
102 if (isset($this->blocks[$node['#tag']])) {
103 if (!isset($this->blocks[$node['#tag']]['layout'])) {
104 $this->blocks[$node['#tag']] = Afform::get()->setCheckPermissions(FALSE)->setSelect(['name', 'layout'])->addWhere('name', '=', $this->blocks[$node['#tag']]['name'])->execute()->first();
105 }
106 if (!empty($this->blocks[$node['#tag']]['layout'])) {
107 $this->parseFields($this->blocks[$node['#tag']]['layout'], $entity, $join);
108 }
fc54326f
TO
109 }
110 }
111 }
112
113 /**
114 * @return array
115 * Ex: $entities['spouse']['type'] = 'Contact';
116 */
117 public function getEntities() {
118 return $this->entities;
119 }
120
121}