Commit | Line | Data |
---|---|---|
0a946de2 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
fee14197 | 4 | | CiviCRM version 5 | |
0a946de2 | 5 | +--------------------------------------------------------------------+ |
6b83d5bd | 6 | | Copyright CiviCRM LLC (c) 2004-2019 | |
0a946de2 TO |
7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
11 | | under the terms of the GNU Affero General Public License | | |
12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | | |
13 | | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more details. | | |
18 | | | | |
19 | | You should have received a copy of the GNU Affero General Public | | |
20 | | License and the CiviCRM Licensing Exception along | | |
21 | | with this program; if not, contact CiviCRM LLC | | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
d25dd0ee | 26 | */ |
0a946de2 TO |
27 | |
28 | namespace Civi\API\Subscriber; | |
46bcf597 | 29 | |
0a946de2 TO |
30 | use Civi\API\Events; |
31 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
32 | ||
33 | /** | |
8882ff5c TO |
34 | * The ChainSubscriber looks for API parameters which specify a nested or |
35 | * chained API call. For example: | |
0a946de2 TO |
36 | * |
37 | * @code | |
38 | * $result = civicrm_api('Contact', 'create', array( | |
39 | * 'version' => 3, | |
40 | * 'first_name' => 'Amy', | |
41 | * 'api.Email.create' => array( | |
42 | * 'email' => 'amy@example.com', | |
43 | * 'location_type_id' => 123, | |
44 | * ), | |
45 | * )); | |
46 | * @endcode | |
47 | * | |
8882ff5c TO |
48 | * The ChainSubscriber looks for any parameters of the form "api.Email.create"; |
49 | * if found, it issues the nested API call (and passes some extra context -- | |
50 | * eg Amy's contact_id). | |
0a946de2 TO |
51 | */ |
52 | class ChainSubscriber implements EventSubscriberInterface { | |
6550386a EM |
53 | /** |
54 | * @return array | |
55 | */ | |
0a946de2 TO |
56 | public static function getSubscribedEvents() { |
57 | return array( | |
58 | Events::RESPOND => array('onApiRespond', Events::W_EARLY), | |
59 | ); | |
60 | } | |
61 | ||
6550386a EM |
62 | /** |
63 | * @param \Civi\API\Event\RespondEvent $event | |
8882ff5c | 64 | * API response event. |
6550386a EM |
65 | * |
66 | * @throws \Exception | |
67 | */ | |
0a946de2 TO |
68 | public function onApiRespond(\Civi\API\Event\RespondEvent $event) { |
69 | $apiRequest = $event->getApiRequest(); | |
8bcc0d86 CW |
70 | if ($apiRequest['version'] < 4) { |
71 | $result = $event->getResponse(); | |
72 | if (\CRM_Utils_Array::value('is_error', $result, 0) == 0) { | |
73 | $this->callNestedApi($event->getApiKernel(), $apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); | |
74 | $event->setResponse($result); | |
75 | } | |
0a946de2 TO |
76 | } |
77 | } | |
78 | ||
79 | /** | |
fe482240 | 80 | * Call any nested api calls. |
0a946de2 TO |
81 | * |
82 | * TODO: We don't really need this to be a separate function. | |
c0e26341 | 83 | * @param \Civi\API\Kernel $apiKernel |
257e7666 EM |
84 | * @param $params |
85 | * @param $result | |
86 | * @param $action | |
87 | * @param $entity | |
88 | * @param $version | |
89 | * @throws \Exception | |
0a946de2 | 90 | */ |
c0e26341 | 91 | protected function callNestedApi($apiKernel, &$params, &$result, $action, $entity, $version) { |
4846df91 | 92 | $lowercase_entity = _civicrm_api_get_entity_name_from_camel($entity); |
0a946de2 | 93 | |
8882ff5c TO |
94 | // We don't need to worry about nested api in the getfields/getoptions |
95 | // actions, so just return immediately. | |
8966acf7 | 96 | if (in_array($action, array('getfields', 'getfield', 'getoptions'))) { |
0a946de2 TO |
97 | return; |
98 | } | |
99 | ||
4846df91 | 100 | if ($action == 'getsingle') { |
0a946de2 TO |
101 | // I don't understand the protocol here, but we don't want |
102 | // $result to be a recursive array | |
103 | // $result['values'][0] = $result; | |
104 | $oldResult = $result; | |
105 | $result = array('values' => array(0 => $oldResult)); | |
106 | } | |
107 | foreach ($params as $field => $newparams) { | |
108 | if ((is_array($newparams) || $newparams === 1) && $field <> 'api.has_parent' && substr($field, 0, 3) == 'api') { | |
109 | ||
8882ff5c TO |
110 | // 'api.participant.delete' => 1 is a valid options - handle 1 |
111 | // instead of an array | |
0a946de2 TO |
112 | if ($newparams === 1) { |
113 | $newparams = array('version' => $version); | |
114 | } | |
115 | // can be api_ or api. | |
116 | $separator = $field[3]; | |
117 | if (!($separator == '.' || $separator == '_')) { | |
118 | continue; | |
119 | } | |
120 | $subAPI = explode($separator, $field); | |
121 | ||
122 | $subaction = empty($subAPI[2]) ? $action : $subAPI[2]; | |
123 | $subParams = array( | |
124 | 'debug' => \CRM_Utils_Array::value('debug', $params), | |
125 | ); | |
4846df91 | 126 | $subEntity = _civicrm_api_get_entity_name_from_camel($subAPI[1]); |
0a946de2 | 127 | |
449fbe14 SL |
128 | // Hard coded list of entitys that have fields starting api_ and shouldn't be automatically |
129 | // deemed to be chained API calls | |
5bc7d8e2 SL |
130 | $skipList = array( |
131 | 'SmsProvider' => array('type', 'url', 'params'), | |
132 | 'Job' => array('prefix', 'entity', 'action'), | |
133 | 'Contact' => array('key'), | |
134 | ); | |
135 | if (isset($skipList[$entity]) && in_array($subEntity, $skipList[$entity])) { | |
449fbe14 SL |
136 | continue; |
137 | } | |
138 | ||
0a946de2 TO |
139 | foreach ($result['values'] as $idIndex => $parentAPIValues) { |
140 | ||
4846df91 | 141 | if ($subEntity != 'contact') { |
0a946de2 | 142 | //contact spits the dummy at activity_id so what else won't it like? |
8882ff5c TO |
143 | //set entity_id & entity table based on the parent's id & entity. |
144 | //e.g for something like note if the parent call is contact | |
145 | //'entity_table' will be set to 'contact' & 'id' to the contact id | |
146 | //from the parent call. in this case 'contact_id' will also be | |
147 | //set to the parent's id | |
4a517906 PN |
148 | if (!($subEntity == 'line_item' && $lowercase_entity == 'contribution' && $action != 'create')) { |
149 | $subParams["entity_id"] = $parentAPIValues['id']; | |
150 | $subParams['entity_table'] = 'civicrm_' . $lowercase_entity; | |
151 | } | |
9960c0ef | 152 | |
602e7157 | 153 | $addEntityId = TRUE; |
9960c0ef JV |
154 | if ($subEntity == 'relationship' && $lowercase_entity == 'contact') { |
155 | // if a relationship call is chained to a contact call, we need | |
156 | // to check whether contact_id_a or contact_id_b for the | |
157 | // relationship is given. If so, don't add an extra subParam | |
158 | // "contact_id" => parent_id. | |
159 | // See CRM-16084. | |
160 | foreach (array_keys($newparams) as $key) { | |
161 | if (substr($key, 0, 11) == 'contact_id_') { | |
602e7157 | 162 | $addEntityId = FALSE; |
9960c0ef JV |
163 | break; |
164 | } | |
165 | } | |
166 | } | |
602e7157 | 167 | if ($addEntityId) { |
9960c0ef JV |
168 | $subParams[$lowercase_entity . "_id"] = $parentAPIValues['id']; |
169 | } | |
0a946de2 | 170 | } |
4846df91 | 171 | if ($entity != 'Contact' && \CRM_Utils_Array::value(strtolower($subEntity . "_id"), $parentAPIValues)) { |
8882ff5c TO |
172 | //e.g. if event_id is in the values returned & subentity is event |
173 | //then pass in event_id as 'id' don't do this for contact as it | |
e4f46be0 | 174 | //does some weird things like returning primary email & |
0a946de2 TO |
175 | //thus limiting the ability to chain email |
176 | //TODO - this might need the camel treatment | |
177 | $subParams['id'] = $parentAPIValues[$subEntity . "_id"]; | |
178 | } | |
179 | ||
180 | if (\CRM_Utils_Array::value('entity_table', $result['values'][$idIndex]) == $subEntity) { | |
181 | $subParams['id'] = $result['values'][$idIndex]['entity_id']; | |
182 | } | |
8882ff5c TO |
183 | // if we are dealing with the same entity pass 'id' through |
184 | // (useful for get + delete for example) | |
4846df91 | 185 | if ($lowercase_entity == $subEntity) { |
0a946de2 TO |
186 | $subParams['id'] = $result['values'][$idIndex]['id']; |
187 | } | |
188 | ||
0a946de2 TO |
189 | $subParams['version'] = $version; |
190 | if (!empty($params['check_permissions'])) { | |
191 | $subParams['check_permissions'] = $params['check_permissions']; | |
192 | } | |
193 | $subParams['sequential'] = 1; | |
194 | $subParams['api.has_parent'] = 1; | |
195 | if (array_key_exists(0, $newparams)) { | |
196 | $genericParams = $subParams; | |
197 | // it is a numerically indexed array - ie. multiple creates | |
198 | foreach ($newparams as $entityparams) { | |
199 | $subParams = array_merge($genericParams, $entityparams); | |
845d6d75 | 200 | _civicrm_api_replace_variables($subParams, $result['values'][$idIndex], $separator); |
7d572f56 | 201 | $result['values'][$idIndex][$field][] = $apiKernel->run($subEntity, $subaction, $subParams); |
0a946de2 TO |
202 | if ($result['is_error'] === 1) { |
203 | throw new \Exception($subEntity . ' ' . $subaction . 'call failed with' . $result['error_message']); | |
204 | } | |
205 | } | |
206 | } | |
207 | else { | |
208 | ||
209 | $subParams = array_merge($subParams, $newparams); | |
845d6d75 | 210 | _civicrm_api_replace_variables($subParams, $result['values'][$idIndex], $separator); |
c0e26341 | 211 | $result['values'][$idIndex][$field] = $apiKernel->run($subEntity, $subaction, $subParams); |
0a946de2 TO |
212 | if (!empty($result['is_error'])) { |
213 | throw new \Exception($subEntity . ' ' . $subaction . 'call failed with' . $result['error_message']); | |
214 | } | |
215 | } | |
216 | } | |
217 | } | |
218 | } | |
4846df91 | 219 | if ($action == 'getsingle') { |
0a946de2 TO |
220 | $result = $result['values'][0]; |
221 | } | |
222 | } | |
223 | ||
6550386a | 224 | } |