Afform - Delegated API calls should use a security helper
[civicrm-core.git] / ext / afform / core / Civi / Api4 / Action / Afform / Submit.php
1 <?php
2
3 namespace Civi\Api4\Action\Afform;
4
5 use Civi\Afform\Event\AfformSubmitEvent;
6
7 /**
8 * Class Submit
9 * @package Civi\Api4\Action\Afform
10 */
11 class Submit extends AbstractProcessor {
12
13 const EVENT_NAME = 'civi.afform.submit';
14
15 /**
16 * Submitted values
17 * @var array
18 * @required
19 */
20 protected $values;
21
22 protected function processForm() {
23 $entityValues = [];
24 foreach ($this->_formDataModel->getEntities() as $entityName => $entity) {
25 foreach ($this->values[$entityName] ?? [] as $values) {
26 $entityValues[$entity['type']][$entityName][] = $values + ['fields' => []];
27 // Predetermined values override submitted values
28 if (!empty($entity['data'])) {
29 foreach ($entityValues[$entity['type']][$entityName] as $index => $vals) {
30 $entityValues[$entity['type']][$entityName][$index]['fields'] = $entity['data'] + $vals['fields'];
31 }
32 }
33 }
34 }
35 $event = new AfformSubmitEvent($this->_afform, $this->_formDataModel, $this, $this->_formDataModel->getEntities(), $entityValues);
36 \Civi::dispatcher()->dispatch(self::EVENT_NAME, $event);
37 foreach ($event->entityValues as $entityType => $entities) {
38 if (!empty($entities)) {
39 throw new \API_Exception(sprintf("Failed to process entities (type=%s; name=%s)", $entityType, implode(',', array_keys($entities))));
40 }
41 }
42
43 // What should I return?
44 return [];
45 }
46
47 /**
48 * @param \Civi\Afform\Event\AfformSubmitEvent $event
49 * @throws \API_Exception
50 * @see afform_civicrm_config
51 */
52 public static function processContacts(AfformSubmitEvent $event) {
53 foreach ($event->entityValues['Contact'] ?? [] as $entityName => $contacts) {
54 $api4 = $event->formDataModel->getSecureApi4($entityName);
55 foreach ($contacts as $contact) {
56 $saved = $api4('Contact', 'save', ['records' => [$contact['fields']]])->first();
57 self::saveJoins($api4, 'Contact', $saved['id'], $contact['joins'] ?? []);
58 }
59 }
60 unset($event->entityValues['Contact']);
61 }
62
63 /**
64 * @param \Civi\Afform\Event\AfformSubmitEvent $event
65 * @throws \API_Exception
66 * @see afform_civicrm_config
67 */
68 public static function processGenericEntity(AfformSubmitEvent $event) {
69 foreach ($event->entityValues as $entityType => $entities) {
70 // Each record is an array of one or more items (can be > 1 if af-repeat is used)
71 foreach ($entities as $entityName => $records) {
72 $api4 = $event->formDataModel->getSecureApi4($entityName);
73 foreach ($records as $record) {
74 $saved = $api4($entityType, 'save', ['records' => [$record['fields']]])->first();
75 self::saveJoins($api4, $entityType, $saved['id'], $record['joins'] ?? []);
76 }
77 }
78 unset($event->entityValues[$entityType]);
79 }
80 }
81
82 protected static function saveJoins($api4, $mainEntityName, $entityId, $joins) {
83 foreach ($joins as $joinEntityName => $join) {
84 $values = self::filterEmptyJoins($joinEntityName, $join);
85 // FIXME: Replace/delete should only be done to known contacts
86 if ($values) {
87 $api4($joinEntityName, 'replace', [
88 'where' => self::getJoinWhereClause($mainEntityName, $joinEntityName, $entityId),
89 'records' => $values,
90 ]);
91 }
92 else {
93 try {
94 $api4($joinEntityName, 'delete', [
95 'where' => self::getJoinWhereClause($mainEntityName, $joinEntityName, $entityId),
96 ]);
97 }
98 catch (\API_Exception $e) {
99 // No records to delete
100 }
101 }
102 }
103 }
104
105 /**
106 * Filter out joins that have been left blank on the form
107 *
108 * @param $entity
109 * @param $join
110 * @return array
111 */
112 private static function filterEmptyJoins($entity, $join) {
113 return array_filter($join, function($item) use($entity) {
114 switch ($entity) {
115 case 'Email':
116 return !empty($item['email']);
117
118 case 'Phone':
119 return !empty($item['phone']);
120
121 case 'IM':
122 return !empty($item['name']);
123
124 case 'Website':
125 return !empty($item['url']);
126
127 default:
128 \CRM_Utils_Array::remove($item, 'id', 'is_primary', 'location_type_id', 'entity_id', 'contact_id', 'entity_table');
129 return (bool) array_filter($item);
130 }
131 });
132 }
133
134 }