Commit | Line | Data |
---|---|---|
6a488035 | 1 | <?php |
6a488035 TO |
2 | /* |
3 | +--------------------------------------------------------------------+ | |
a30c801b | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
a30c801b TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 TO |
9 | +--------------------------------------------------------------------+ |
10 | */ | |
11 | ||
12 | /** | |
244bbdd8 | 13 | * This api exposes CiviCRM Activity records. |
6a488035 TO |
14 | * |
15 | * @package CiviCRM_APIv3 | |
6a488035 TO |
16 | */ |
17 | ||
6a488035 | 18 | /** |
61fe4988 | 19 | * Creates or updates an Activity. |
6a488035 | 20 | * |
cf470720 | 21 | * @param array $params |
c28e1768 | 22 | * Array per getfields documentation. |
6a488035 | 23 | * |
77b97be7 | 24 | * @throws API_Exception |
a6c01b45 | 25 | * @return array |
00f8641b | 26 | * API result array |
6a488035 TO |
27 | */ |
28 | function civicrm_api3_activity_create($params) { | |
c16b4619 | 29 | $isNew = empty($params['id']); |
6a488035 | 30 | |
a7488080 | 31 | if (empty($params['id'])) { |
6a488035 TO |
32 | // an update does not require any mandatory parameters |
33 | civicrm_api3_verify_one_mandatory($params, | |
34 | NULL, | |
cf8f0fff | 35 | [ |
7cdbcb16 TO |
36 | 'activity_name', |
37 | 'activity_type_id', | |
38 | 'activity_label', | |
cf8f0fff | 39 | ] |
6a488035 TO |
40 | ); |
41 | } | |
42 | ||
6a488035 TO |
43 | // check for various error and required conditions |
44 | // note that almost all the processing in there should be managed by the wrapper layer | |
45 | // & should be removed - needs testing | |
46 | $errors = _civicrm_api3_activity_check_params($params); | |
47 | ||
48 | // this should not be required as should throw exception rather than return errors - | |
49 | //needs testing | |
50 | if (!empty($errors)) { | |
51 | return $errors; | |
52 | } | |
53 | ||
6a488035 | 54 | // processing for custom data |
cf8f0fff | 55 | $values = $activityArray = []; |
6a488035 TO |
56 | _civicrm_api3_custom_format_params($params, $values, 'Activity'); |
57 | ||
58 | if (!empty($values['custom'])) { | |
59 | $params['custom'] = $values['custom']; | |
60 | } | |
61 | ||
62 | // this should be set as a default rather than hard coded | |
63 | // needs testing | |
64 | $params['skipRecentView'] = TRUE; | |
65 | ||
66 | // If this is a case activity, see if there is an existing activity | |
67 | // and set it as an old revision. Also retrieve details we'll need. | |
68 | // this handling should all be moved to the BAO layer | |
7cdbcb16 TO |
69 | $case_id = ''; |
70 | $createRevision = FALSE; | |
cf8f0fff | 71 | $oldActivityValues = []; |
e96d5fa3 CW |
72 | // Lookup case id if not supplied |
73 | if (!isset($params['case_id']) && !empty($params['id'])) { | |
74 | $params['case_id'] = CRM_Core_DAO::singleValueQuery("SELECT case_id FROM civicrm_case_activity WHERE activity_id = " . (int) $params['id']); | |
75 | } | |
a7488080 | 76 | if (!empty($params['case_id'])) { |
6a488035 | 77 | $case_id = $params['case_id']; |
c16b4619 | 78 | if (!empty($params['id']) && Civi::settings()->get('civicaseActivityRevisions')) { |
cf8f0fff | 79 | $oldActivityParams = ['id' => $params['id']]; |
6a488035 TO |
80 | if (!$oldActivityValues) { |
81 | CRM_Activity_BAO_Activity::retrieve($oldActivityParams, $oldActivityValues); | |
82 | } | |
83 | if (empty($oldActivityValues)) { | |
10114f2d | 84 | throw new API_Exception(ts("Unable to locate existing activity.")); |
6a488035 TO |
85 | } |
86 | else { | |
87 | $activityDAO = new CRM_Activity_DAO_Activity(); | |
88 | $activityDAO->id = $params['id']; | |
89 | $activityDAO->is_current_revision = 0; | |
90 | if (!$activityDAO->save()) { | |
10114f2d | 91 | throw new API_Exception(ts("Unable to revision existing case activity.")); |
6a488035 TO |
92 | } |
93 | $createRevision = TRUE; | |
94 | } | |
95 | } | |
96 | } | |
97 | ||
98 | $deleteActivityAssignment = FALSE; | |
99 | if (isset($params['assignee_contact_id'])) { | |
100 | $deleteActivityAssignment = TRUE; | |
101 | } | |
102 | ||
103 | $deleteActivityTarget = FALSE; | |
104 | if (isset($params['target_contact_id'])) { | |
105 | $deleteActivityTarget = TRUE; | |
106 | } | |
107 | ||
108 | // this should all be handled at the BAO layer | |
109 | $params['deleteActivityAssignment'] = CRM_Utils_Array::value('deleteActivityAssignment', $params, $deleteActivityAssignment); | |
110 | $params['deleteActivityTarget'] = CRM_Utils_Array::value('deleteActivityTarget', $params, $deleteActivityTarget); | |
111 | ||
112 | if ($case_id && $createRevision) { | |
113 | // This is very similar to the copy-to-case action. | |
114 | if (!CRM_Utils_Array::crmIsEmptyArray($oldActivityValues['target_contact'])) { | |
115 | $oldActivityValues['targetContactIds'] = implode(',', array_unique($oldActivityValues['target_contact'])); | |
116 | } | |
117 | if (!CRM_Utils_Array::crmIsEmptyArray($oldActivityValues['assignee_contact'])) { | |
118 | $oldActivityValues['assigneeContactIds'] = implode(',', array_unique($oldActivityValues['assignee_contact'])); | |
119 | } | |
120 | $oldActivityValues['mode'] = 'copy'; | |
121 | $oldActivityValues['caseID'] = $case_id; | |
122 | $oldActivityValues['activityID'] = $oldActivityValues['id']; | |
123 | $oldActivityValues['contactID'] = $oldActivityValues['source_contact_id']; | |
124 | ||
125 | $copyToCase = CRM_Activity_Page_AJAX::_convertToCaseActivity($oldActivityValues); | |
126 | if (empty($copyToCase['error_msg'])) { | |
127 | // now fix some things that are different from copy-to-case | |
128 | // then fall through to the create below to update with the passed in params | |
129 | $params['id'] = $copyToCase['newId']; | |
130 | $params['is_auto'] = 0; | |
131 | $params['original_id'] = empty($oldActivityValues['original_id']) ? $oldActivityValues['id'] : $oldActivityValues['original_id']; | |
132 | } | |
133 | else { | |
10114f2d | 134 | throw new API_Exception(ts("Unable to create new revision of case activity.")); |
6a488035 TO |
135 | } |
136 | } | |
137 | ||
138 | // create activity | |
139 | $activityBAO = CRM_Activity_BAO_Activity::create($params); | |
140 | ||
141 | if (isset($activityBAO->id)) { | |
c16b4619 | 142 | if ($case_id && $isNew && !$createRevision) { |
18e0f096 CW |
143 | // If this is a brand new case activity, add to case(s) |
144 | foreach ((array) $case_id as $singleCaseId) { | |
cf8f0fff | 145 | $caseActivityParams = ['activity_id' => $activityBAO->id, 'case_id' => $singleCaseId]; |
18e0f096 CW |
146 | CRM_Case_BAO_Case::processCaseActivity($caseActivityParams); |
147 | } | |
6a488035 TO |
148 | } |
149 | ||
150 | _civicrm_api3_object_to_array($activityBAO, $activityArray[$activityBAO->id]); | |
244bbdd8 | 151 | return civicrm_api3_create_success($activityArray, $params, 'Activity', 'get', $activityBAO); |
6a488035 TO |
152 | } |
153 | } | |
11e09c59 TO |
154 | |
155 | /** | |
61fe4988 EM |
156 | * Specify Meta data for create. |
157 | * | |
158 | * Note that this data is retrievable via the getfields function and is used for pre-filling defaults and | |
159 | * ensuring mandatory requirements are met. | |
160 | * | |
cf470720 | 161 | * @param array $params |
c28e1768 | 162 | * Array of parameters determined by getfields. |
6a488035 TO |
163 | */ |
164 | function _civicrm_api3_activity_create_spec(&$params) { | |
165 | ||
cf8f0fff | 166 | $params['status_id']['api.aliases'] = ['activity_status']; |
67744c4e | 167 | |
cf8f0fff | 168 | $params['assignee_contact_id'] = [ |
6a488035 | 169 | 'name' => 'assignee_id', |
0a24dc9a CW |
170 | 'title' => 'Activity Assignee', |
171 | 'description' => 'Contact(s) assigned to this activity.', | |
6a488035 | 172 | 'type' => 1, |
7e61908b | 173 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
0a24dc9a | 174 | 'FKApiName' => 'Contact', |
cf8f0fff CW |
175 | ]; |
176 | $params['target_contact_id'] = [ | |
6a488035 TO |
177 | 'name' => 'target_id', |
178 | 'title' => 'Activity Target', | |
0a24dc9a | 179 | 'description' => 'Contact(s) participating in this activity.', |
6a488035 | 180 | 'type' => 1, |
7e61908b | 181 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
0a24dc9a | 182 | 'FKApiName' => 'Contact', |
cf8f0fff | 183 | ]; |
2f3d72cf | 184 | |
cf8f0fff | 185 | $params['source_contact_id'] = [ |
7cdbcb16 TO |
186 | 'name' => 'source_contact_id', |
187 | 'title' => 'Activity Source Contact', | |
0a24dc9a | 188 | 'description' => 'Person who created this activity. Defaults to current user.', |
7cdbcb16 | 189 | 'type' => 1, |
7e61908b | 190 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
7cdbcb16 | 191 | 'api.default' => 'user_contact_id', |
0a24dc9a | 192 | 'FKApiName' => 'Contact', |
c442f1b6 | 193 | 'api.required' => TRUE, |
cf8f0fff | 194 | ]; |
0a24dc9a | 195 | |
cf8f0fff | 196 | $params['case_id'] = [ |
0a24dc9a CW |
197 | 'name' => 'case_id', |
198 | 'title' => 'Case ID', | |
199 | 'description' => 'For creating an activity as part of a case.', | |
200 | 'type' => 1, | |
201 | 'FKClassName' => 'CRM_Case_DAO_Case', | |
202 | 'FKApiName' => 'Case', | |
cf8f0fff | 203 | ]; |
2f3d72cf | 204 | |
6a488035 TO |
205 | } |
206 | ||
1196e086 CW |
207 | /** |
208 | * Specify Metadata for get. | |
209 | * | |
210 | * @param array $params | |
211 | */ | |
212 | function _civicrm_api3_activity_get_spec(&$params) { | |
cf8f0fff | 213 | $params['tag_id'] = [ |
1196e086 CW |
214 | 'title' => 'Tags', |
215 | 'description' => 'Find activities with specified tags.', | |
760ac501 | 216 | 'type' => CRM_Utils_Type::T_INT, |
1196e086 CW |
217 | 'FKClassName' => 'CRM_Core_DAO_Tag', |
218 | 'FKApiName' => 'Tag', | |
40875a91 | 219 | 'supports_joins' => TRUE, |
cf8f0fff CW |
220 | ]; |
221 | $params['file_id'] = [ | |
40875a91 CW |
222 | 'title' => 'Attached Files', |
223 | 'description' => 'Find activities with attached files.', | |
760ac501 | 224 | 'type' => CRM_Utils_Type::T_INT, |
40875a91 CW |
225 | 'FKClassName' => 'CRM_Core_DAO_File', |
226 | 'FKApiName' => 'File', | |
cf8f0fff CW |
227 | ]; |
228 | $params['case_id'] = [ | |
1196e086 CW |
229 | 'title' => 'Cases', |
230 | 'description' => 'Find activities within specified cases.', | |
760ac501 | 231 | 'type' => CRM_Utils_Type::T_INT, |
1196e086 CW |
232 | 'FKClassName' => 'CRM_Case_DAO_Case', |
233 | 'FKApiName' => 'Case', | |
2c9e4446 | 234 | 'supports_joins' => TRUE, |
cf8f0fff CW |
235 | ]; |
236 | $params['contact_id'] = [ | |
526e0834 CW |
237 | 'title' => 'Activity Contact ID', |
238 | 'description' => 'Find activities involving this contact (as target, source, OR assignee).', | |
760ac501 | 239 | 'type' => CRM_Utils_Type::T_INT, |
526e0834 CW |
240 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
241 | 'FKApiName' => 'Contact', | |
cf8f0fff CW |
242 | ]; |
243 | $params['target_contact_id'] = [ | |
1196e086 CW |
244 | 'title' => 'Target Contact ID', |
245 | 'description' => 'Find activities with specified target contact.', | |
760ac501 | 246 | 'type' => CRM_Utils_Type::T_INT, |
1196e086 CW |
247 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
248 | 'FKApiName' => 'Contact', | |
cf8f0fff CW |
249 | ]; |
250 | $params['source_contact_id'] = [ | |
1196e086 CW |
251 | 'title' => 'Source Contact ID', |
252 | 'description' => 'Find activities with specified source contact.', | |
760ac501 | 253 | 'type' => CRM_Utils_Type::T_INT, |
1196e086 CW |
254 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
255 | 'FKApiName' => 'Contact', | |
cf8f0fff CW |
256 | ]; |
257 | $params['assignee_contact_id'] = [ | |
1196e086 CW |
258 | 'title' => 'Assignee Contact ID', |
259 | 'description' => 'Find activities with specified assignee contact.', | |
760ac501 | 260 | 'type' => CRM_Utils_Type::T_INT, |
1196e086 CW |
261 | 'FKClassName' => 'CRM_Contact_DAO_Contact', |
262 | 'FKApiName' => 'Contact', | |
cf8f0fff CW |
263 | ]; |
264 | $params['is_overdue'] = [ | |
760ac501 CW |
265 | 'title' => 'Is Activity Overdue', |
266 | 'description' => 'Incomplete activities with a past date.', | |
267 | 'type' => CRM_Utils_Type::T_BOOLEAN, | |
cf8f0fff | 268 | ]; |
1196e086 CW |
269 | } |
270 | ||
6a488035 | 271 | /** |
61fe4988 | 272 | * Gets a CiviCRM activity according to parameters. |
6a488035 | 273 | * |
cf470720 | 274 | * @param array $params |
61fe4988 | 275 | * Array per getfields documentation. |
6a488035 | 276 | * |
7c31ae57 | 277 | * @return array |
00f8641b | 278 | * API result array |
bbd2743b | 279 | * |
280 | * @throws \API_Exception | |
281 | * @throws \CiviCRM_API3_Exception | |
282 | * @throws \Civi\API\Exception\UnauthorizedException | |
6a488035 TO |
283 | */ |
284 | function civicrm_api3_activity_get($params) { | |
760ac501 | 285 | $options = _civicrm_api3_get_options_from_params($params, FALSE, 'Activity', 'get'); |
526e0834 | 286 | $sql = CRM_Utils_SQL_Select::fragment(); |
a6c2ebdc | 287 | _civicrm_activity_get_handleSourceContactNameOrderBy($params, $options, $sql); |
d544ffcd | 288 | |
d544ffcd CW |
289 | _civicrm_api3_activity_get_extraFilters($params, $sql); |
290 | ||
291 | // Handle is_overdue sort | |
292 | if (!empty($options['sort'])) { | |
293 | $sort = explode(', ', $options['sort']); | |
294 | ||
295 | foreach ($sort as $index => &$sortString) { | |
296 | // Get sort field and direction | |
297 | list($sortField, $dir) = array_pad(explode(' ', $sortString), 2, 'ASC'); | |
298 | if ($sortField == 'is_overdue') { | |
299 | $incomplete = implode(',', array_keys(CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::INCOMPLETE))); | |
300 | $sql->orderBy("IF((a.activity_date_time >= NOW() OR a.status_id NOT IN ($incomplete)), 0, 1) $dir", NULL, $index); | |
301 | // Replace the sort with a placeholder which will be ignored by sql | |
302 | $sortString = '(1)'; | |
303 | } | |
304 | } | |
305 | $params['options']['sort'] = implode(', ', $sort); | |
306 | } | |
307 | ||
308 | // Ensure there's enough data for calculating is_overdue | |
309 | if (!empty($options['return']['is_overdue']) && (empty($options['return']['status_id']) || empty($options['return']['activity_date_time']))) { | |
310 | $options['return']['status_id'] = $options['return']['activity_date_time'] = 1; | |
311 | $params['return'] = array_keys($options['return']); | |
312 | } | |
313 | ||
314 | $activities = _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params, FALSE, 'Activity', $sql); | |
84be264e | 315 | if ($options['is_count']) { |
316 | return civicrm_api3_create_success($activities, $params, 'Activity', 'get'); | |
317 | } | |
318 | ||
d544ffcd CW |
319 | $activities = _civicrm_api3_activity_get_formatResult($params, $activities, $options); |
320 | //legacy custom data get - so previous formatted response is still returned too | |
321 | return civicrm_api3_create_success($activities, $params, 'Activity', 'get'); | |
322 | } | |
323 | ||
a6c2ebdc | 324 | /** |
325 | * Handle source_contact_name as a sort parameter. | |
326 | * | |
327 | * This is passed from the activity selector - e.g search results or contact tab. | |
328 | * | |
329 | * It's a non-standard handling but this api already handles variations on handling source_contact | |
330 | * as a filter & as a field so it's in keeping with that. Source contact has a one-one relationship | |
331 | * with activity table. | |
332 | * | |
333 | * Test coverage in CRM_Activity_BAO_ActivtiyTest::testGetActivitiesforContactSummaryWithSortOptions | |
334 | * | |
335 | * @param array $params | |
336 | * @param array $options | |
337 | * @param CRM_Utils_SQL_Select $sql | |
338 | */ | |
339 | function _civicrm_activity_get_handleSourceContactNameOrderBy(&$params, &$options, $sql) { | |
340 | $sourceContactID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Source'); | |
4a413eb6 AH |
341 | if (!empty($options['sort']) |
342 | && in_array($options['sort'], [ | |
a6c2ebdc | 343 | 'source_contact_name', |
344 | 'source_contact_name desc', | |
4a413eb6 | 345 | 'source_contact_name asc', |
a6c2ebdc | 346 | ])) { |
347 | $order = substr($options['sort'], -4) === 'desc' ? 'desc' : 'asc'; | |
348 | $sql->join( | |
349 | 'source_contact', | |
350 | "LEFT JOIN | |
c2068a3e TO |
351 | civicrm_activity_contact ac ON (ac.activity_id = a.id AND record_type_id = #sourceContactID) |
352 | LEFT JOIN civicrm_contact c ON c.id = ac.contact_id", | |
353 | ['sourceContactID' => $sourceContactID] | |
a6c2ebdc | 354 | ); |
355 | $sql->orderBy("c.display_name $order"); | |
356 | unset($options['sort'], $params['options']['sort']); | |
357 | } | |
358 | } | |
359 | ||
d544ffcd CW |
360 | /** |
361 | * Support filters beyond what basic_get can do. | |
362 | * | |
363 | * @param array $params | |
364 | * @param CRM_Utils_SQL_Select $sql | |
365 | * @throws \CiviCRM_API3_Exception | |
366 | * @throws \Exception | |
367 | */ | |
368 | function _civicrm_api3_activity_get_extraFilters(&$params, &$sql) { | |
369 | // Filter by activity contacts | |
cf8f0fff | 370 | $activityContactOptions = [ |
d544ffcd | 371 | 'contact_id' => NULL, |
550dcde8 | 372 | 'target_contact_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'), |
373 | 'source_contact_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Source'), | |
374 | 'assignee_contact_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Assignees'), | |
cf8f0fff | 375 | ]; |
526e0834 CW |
376 | foreach ($activityContactOptions as $activityContactName => $activityContactValue) { |
377 | if (!empty($params[$activityContactName])) { | |
526e0834 | 378 | if (!is_array($params[$activityContactName])) { |
cf8f0fff | 379 | $params[$activityContactName] = ['=' => $params[$activityContactName]]; |
526e0834 CW |
380 | } |
381 | $clause = \CRM_Core_DAO::createSQLFilter('contact_id', $params[$activityContactName]); | |
382 | $typeClause = $activityContactValue ? 'record_type_id = #typeId AND ' : ''; | |
383 | $sql->where("a.id IN (SELECT activity_id FROM civicrm_activity_contact WHERE $typeClause !clause)", | |
cf8f0fff | 384 | ['#typeId' => $activityContactValue, '!clause' => $clause] |
526e0834 | 385 | ); |
6a488035 TO |
386 | } |
387 | } | |
749522a2 | 388 | |
760ac501 | 389 | // Handle is_overdue filter |
d544ffcd | 390 | // Boolean calculated field - does not support operators |
760ac501 | 391 | if (isset($params['is_overdue'])) { |
ce9d78e1 | 392 | $incomplete = implode(',', array_keys(CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::INCOMPLETE))); |
760ac501 CW |
393 | if ($params['is_overdue']) { |
394 | $sql->where('a.activity_date_time < NOW()'); | |
ce9d78e1 | 395 | $sql->where("a.status_id IN ($incomplete)"); |
760ac501 CW |
396 | } |
397 | else { | |
ce9d78e1 | 398 | $sql->where("(a.activity_date_time >= NOW() OR a.status_id NOT IN ($incomplete))"); |
760ac501 CW |
399 | } |
400 | } | |
401 | ||
749522a2 TO |
402 | // Define how to handle filters on some related entities. |
403 | // Subqueries are nice in (a) avoiding duplicates and (b) when the result | |
404 | // list is expected to be bite-sized. Joins are nice (a) with larger | |
405 | // datasets and (b) checking for non-existent relations. | |
cf8f0fff CW |
406 | $rels = [ |
407 | 'tag_id' => [ | |
749522a2 TO |
408 | 'subquery' => 'a.id IN (SELECT entity_id FROM civicrm_entity_tag WHERE entity_table = "civicrm_activity" AND !clause)', |
409 | 'join' => '!joinType civicrm_entity_tag !alias ON (!alias.entity_table = "civicrm_activity" AND !alias.entity_id = a.id)', | |
410 | 'column' => 'tag_id', | |
cf8f0fff CW |
411 | ], |
412 | 'file_id' => [ | |
749522a2 TO |
413 | 'subquery' => 'a.id IN (SELECT entity_id FROM civicrm_entity_file WHERE entity_table = "civicrm_activity" AND !clause)', |
414 | 'join' => '!joinType civicrm_entity_file !alias ON (!alias.entity_table = "civicrm_activity" AND !alias.entity_id = a.id)', | |
415 | 'column' => 'file_id', | |
cf8f0fff CW |
416 | ], |
417 | 'case_id' => [ | |
749522a2 TO |
418 | 'subquery' => 'a.id IN (SELECT activity_id FROM civicrm_case_activity WHERE !clause)', |
419 | 'join' => '!joinType civicrm_case_activity !alias ON (!alias.activity_id = a.id)', | |
420 | 'column' => 'case_id', | |
cf8f0fff CW |
421 | ], |
422 | ]; | |
749522a2 TO |
423 | foreach ($rels as $filter => $relSpec) { |
424 | if (!empty($params[$filter])) { | |
425 | if (!is_array($params[$filter])) { | |
cf8f0fff | 426 | $params[$filter] = ['=' => $params[$filter]]; |
749522a2 TO |
427 | } |
428 | // $mode is one of ('LEFT JOIN', 'INNER JOIN', 'SUBQUERY') | |
429 | $mode = isset($params[$filter]['IS NULL']) ? 'LEFT JOIN' : 'SUBQUERY'; | |
430 | if ($mode === 'SUBQUERY') { | |
431 | $clause = \CRM_Core_DAO::createSQLFilter($relSpec['column'], $params[$filter]); | |
432 | if ($clause) { | |
cf8f0fff | 433 | $sql->where($relSpec['subquery'], ['!clause' => $clause]); |
749522a2 TO |
434 | } |
435 | } | |
436 | else { | |
437 | $alias = 'actjoin_' . $filter; | |
438 | $clause = \CRM_Core_DAO::createSQLFilter($alias . "." . $relSpec['column'], $params[$filter]); | |
439 | if ($clause) { | |
cf8f0fff | 440 | $sql->join($alias, $relSpec['join'], ['!alias' => $alias, 'joinType' => $mode]); |
749522a2 TO |
441 | $sql->where($clause); |
442 | } | |
443 | } | |
0298287b | 444 | } |
6a488035 | 445 | } |
ab5fa8f2 TO |
446 | } |
447 | ||
448 | /** | |
61fe4988 | 449 | * Given a list of activities, append any extra data requested about the activities. |
ab5fa8f2 | 450 | * |
b081365f | 451 | * @note Called by civicrm-core and CiviHR |
ab5fa8f2 | 452 | * |
cf470720 TO |
453 | * @param array $params |
454 | * API request parameters. | |
ab5fa8f2 | 455 | * @param array $activities |
cdacd6ab | 456 | * @param array $options |
457 | * Options array (pre-processed to extract 'return' from params). | |
61fe4988 | 458 | * |
a6c01b45 | 459 | * @return array |
72b3a70c | 460 | * new activities list |
ab5fa8f2 | 461 | */ |
bc4b6f0f | 462 | function _civicrm_api3_activity_get_formatResult($params, $activities, $options) { |
30db5cbf CW |
463 | if (!$activities) { |
464 | return $activities; | |
465 | } | |
466 | ||
bc4b6f0f | 467 | $returns = $options['return']; |
6a488035 | 468 | foreach ($params as $n => $v) { |
cdacd6ab | 469 | // @todo - the per-parsing on options should have already done this. |
6a488035 TO |
470 | if (substr($n, 0, 7) == 'return.') { |
471 | $returnkey = substr($n, 7); | |
472 | $returns[$returnkey] = $v; | |
473 | } | |
474 | } | |
0298287b | 475 | |
cdacd6ab | 476 | _civicrm_api3_activity_fill_activity_contact_names($activities, $params, $returns); |
db6e8cb4 | 477 | |
cf8f0fff CW |
478 | $tagGet = ['tag_id', 'entity_id']; |
479 | $caseGet = $caseIds = []; | |
40875a91 CW |
480 | foreach (array_keys($returns) as $key) { |
481 | if (strpos($key, 'tag_id.') === 0) { | |
482 | $tagGet[] = $key; | |
483 | $returns['tag_id'] = 1; | |
484 | } | |
2c9e4446 CW |
485 | if (strpos($key, 'case_id.') === 0) { |
486 | $caseGet[] = str_replace('case_id.', '', $key); | |
487 | $returns['case_id'] = 1; | |
488 | } | |
40875a91 CW |
489 | } |
490 | ||
6a488035 TO |
491 | foreach ($returns as $n => $v) { |
492 | switch ($n) { | |
493 | case 'assignee_contact_id': | |
6a488035 | 494 | case 'target_contact_id': |
cdacd6ab | 495 | foreach ($activities as &$activity) { |
496 | if (!isset($activity[$n])) { | |
497 | $activity[$n] = []; | |
db6e8cb4 | 498 | } |
6a488035 | 499 | } |
35671d00 | 500 | |
42d30b83 | 501 | case 'source_contact_id': |
42d30b83 | 502 | break; |
35671d00 | 503 | |
30db5cbf | 504 | case 'tag_id': |
cf8f0fff | 505 | $tags = civicrm_api3('EntityTag', 'get', [ |
30db5cbf | 506 | 'entity_table' => 'civicrm_activity', |
cf8f0fff | 507 | 'entity_id' => ['IN' => array_keys($activities)], |
40875a91 | 508 | 'return' => $tagGet, |
cf8f0fff CW |
509 | 'options' => ['limit' => 0], |
510 | ]); | |
30db5cbf | 511 | foreach ($tags['values'] as $tag) { |
40875a91 CW |
512 | $key = (int) $tag['entity_id']; |
513 | unset($tag['entity_id'], $tag['id']); | |
514 | $activities[$key]['tag_id'][$tag['tag_id']] = $tag; | |
515 | } | |
516 | break; | |
517 | ||
518 | case 'file_id': | |
519 | $dao = CRM_Core_DAO::executeQuery("SELECT entity_id, file_id FROM civicrm_entity_file WHERE entity_table = 'civicrm_activity' AND entity_id IN (%1)", | |
cf8f0fff | 520 | [1 => [implode(',', array_keys($activities)), 'String', CRM_Core_DAO::QUERY_FORMAT_NO_QUOTES]]); |
40875a91 CW |
521 | while ($dao->fetch()) { |
522 | $activities[$dao->entity_id]['file_id'][] = $dao->file_id; | |
30db5cbf CW |
523 | } |
524 | break; | |
525 | ||
bc4b6f0f CW |
526 | case 'case_id': |
527 | $dao = CRM_Core_DAO::executeQuery("SELECT activity_id, case_id FROM civicrm_case_activity WHERE activity_id IN (%1)", | |
cf8f0fff | 528 | [1 => [implode(',', array_keys($activities)), 'String', CRM_Core_DAO::QUERY_FORMAT_NO_QUOTES]]); |
bc4b6f0f | 529 | while ($dao->fetch()) { |
18e0f096 | 530 | $activities[$dao->activity_id]['case_id'][] = $dao->case_id; |
2c9e4446 | 531 | $caseIds[$dao->case_id] = $dao->case_id; |
bc4b6f0f CW |
532 | } |
533 | break; | |
534 | ||
760ac501 CW |
535 | case 'is_overdue': |
536 | foreach ($activities as $key => $activityArray) { | |
537 | $activities[$key]['is_overdue'] = (int) CRM_Activity_BAO_Activity::isOverdue($activityArray); | |
538 | } | |
539 | break; | |
540 | ||
6a488035 TO |
541 | default: |
542 | if (substr($n, 0, 6) == 'custom') { | |
543 | $returnProperties[$n] = $v; | |
544 | } | |
2c9e4446 CW |
545 | } |
546 | } | |
547 | ||
548 | // Fetch case fields via the join syntax | |
549 | // Note this is limited to the first case if the activity belongs to more than one | |
550 | if ($caseGet && $caseIds) { | |
cf8f0fff CW |
551 | $cases = civicrm_api3('Case', 'get', [ |
552 | 'id' => ['IN' => $caseIds], | |
553 | 'options' => ['limit' => 0], | |
2c9e4446 CW |
554 | 'check_permissions' => !empty($params['check_permissions']), |
555 | 'return' => $caseGet, | |
cf8f0fff | 556 | ]); |
2c9e4446 CW |
557 | foreach ($activities as &$activity) { |
558 | if (!empty($activity['case_id'])) { | |
559 | $case = CRM_Utils_Array::value($activity['case_id'][0], $cases['values']); | |
560 | if ($case) { | |
561 | foreach ($case as $key => $value) { | |
562 | if ($key != 'id') { | |
563 | $activity['case_id.' . $key] = $value; | |
564 | } | |
565 | } | |
566 | } | |
567 | } | |
6a488035 TO |
568 | } |
569 | } | |
526e0834 | 570 | |
40875a91 | 571 | // Legacy extras |
526e0834 CW |
572 | if (!empty($params['contact_id'])) { |
573 | $statusOptions = CRM_Activity_BAO_Activity::buildOptions('status_id', 'get'); | |
574 | $typeOptions = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate'); | |
575 | foreach ($activities as $key => &$activityArray) { | |
576 | if (!empty($activityArray['status_id'])) { | |
577 | $activityArray['status'] = $statusOptions[$activityArray['status_id']]; | |
578 | } | |
579 | if (!empty($activityArray['activity_type_id'])) { | |
580 | $activityArray['activity_name'] = $typeOptions[$activityArray['activity_type_id']]; | |
581 | } | |
582 | } | |
583 | } | |
584 | ||
30db5cbf | 585 | if (!empty($returnProperties) || !empty($params['contact_id'])) { |
6a488035 | 586 | foreach ($activities as $activityId => $values) { |
10114f2d | 587 | //@todo - should possibly load activity type id if not loaded (update with id) |
e9ff5391 | 588 | _civicrm_api3_custom_data_get($activities[$activityId], CRM_Utils_Array::value('check_permissions', $params), 'Activity', $activityId, NULL, CRM_Utils_Array::value('activity_type_id', $values)); |
6a488035 TO |
589 | } |
590 | } | |
ab5fa8f2 | 591 | return $activities; |
6a488035 | 592 | } |
cdacd6ab | 593 | |
594 | /** | |
595 | * Append activity contact details to activity results. | |
596 | * | |
597 | * Adds id & name of activity contacts to results array if check_permissions | |
598 | * does not block access to them. | |
599 | * | |
600 | * For historical reasons source_contact_id is always added & is not an array. | |
601 | * The others are added depending on requested return params. | |
602 | * | |
603 | * @param array $activities | |
604 | * @param array $params | |
605 | * @param array $returns | |
606 | */ | |
607 | function _civicrm_api3_activity_fill_activity_contact_names(&$activities, $params, $returns) { | |
608 | $contactTypes = array_flip(CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate')); | |
609 | $assigneeType = $contactTypes['Activity Assignees']; | |
610 | $targetType = $contactTypes['Activity Targets']; | |
611 | $sourceType = $contactTypes['Activity Source']; | |
612 | $typeMap = [ | |
613 | $assigneeType => 'assignee', | |
614 | $sourceType => 'source', | |
7c31ae57 | 615 | $targetType => 'target', |
cdacd6ab | 616 | ]; |
617 | ||
618 | $activityContactTypes = [$sourceType]; | |
619 | ||
620 | if (!empty($returns['target_contact_name']) || !empty($returns['target_contact_id'])) { | |
621 | $activityContactTypes[] = $targetType; | |
622 | } | |
623 | if (!empty($returns['assignee_contact_name']) || (!empty($returns['assignee_contact_id']))) { | |
624 | $activityContactTypes[] = $assigneeType; | |
625 | } | |
626 | $activityContactParams = [ | |
627 | 'activity_id' => ['IN' => array_keys($activities)], | |
628 | 'return' => [ | |
629 | 'activity_id', | |
630 | 'record_type_id', | |
631 | 'contact_id.display_name', | |
c2ce41b6 | 632 | 'contact_id.sort_name', |
7c31ae57 | 633 | 'contact_id', |
cdacd6ab | 634 | ], |
c2ce41b6 | 635 | 'options' => ['limit' => 0], |
cdacd6ab | 636 | 'check_permissions' => !empty($params['check_permissions']), |
637 | ]; | |
638 | if (count($activityContactTypes) < 3) { | |
639 | $activityContactParams['record_type_id'] = ['IN' => $activityContactTypes]; | |
640 | } | |
641 | $activityContacts = civicrm_api3('ActivityContact', 'get', $activityContactParams)['values']; | |
642 | foreach ($activityContacts as $activityContact) { | |
643 | $contactID = $activityContact['contact_id']; | |
644 | $recordType = $typeMap[$activityContact['record_type_id']]; | |
645 | if (in_array($recordType, ['target', 'assignee'])) { | |
646 | $activities[$activityContact['activity_id']][$recordType . '_contact_id'][] = $contactID; | |
647 | $activities[$activityContact['activity_id']][$recordType . '_contact_name'][$contactID] = isset($activityContact['contact_id.display_name']) ? $activityContact['contact_id.display_name'] : ''; | |
c2ce41b6 | 648 | $activities[$activityContact['activity_id']][$recordType . '_contact_sort_name'][$contactID] = isset($activityContact['contact_id.sort_name']) ? $activityContact['contact_id.sort_name'] : ''; |
cdacd6ab | 649 | } |
650 | else { | |
651 | $activities[$activityContact['activity_id']]['source_contact_id'] = $contactID; | |
652 | $activities[$activityContact['activity_id']]['source_contact_name'] = isset($activityContact['contact_id.display_name']) ? $activityContact['contact_id.display_name'] : ''; | |
c2ce41b6 | 653 | $activities[$activityContact['activity_id']]['source_contact_sort_name'] = isset($activityContact['contact_id.sort_name']) ? $activityContact['contact_id.sort_name'] : ''; |
cdacd6ab | 654 | } |
655 | } | |
656 | } | |
6a488035 TO |
657 | |
658 | /** | |
659 | * Delete a specified Activity. | |
660 | * | |
cf470720 TO |
661 | * @param array $params |
662 | * Array holding 'id' of activity to be deleted. | |
6a488035 | 663 | * |
10114f2d | 664 | * @throws API_Exception |
6a488035 | 665 | * |
61fe4988 | 666 | * @return array |
00f8641b | 667 | * API result array |
6a488035 TO |
668 | */ |
669 | function civicrm_api3_activity_delete($params) { | |
670 | ||
671 | if (CRM_Activity_BAO_Activity::deleteActivity($params)) { | |
244bbdd8 | 672 | return civicrm_api3_create_success(1, $params, 'Activity', 'delete'); |
6a488035 TO |
673 | } |
674 | else { | |
df1dded9 | 675 | throw new API_Exception('Could not delete Activity: ' . (int) $params['id']); |
6a488035 TO |
676 | } |
677 | } | |
678 | ||
679 | /** | |
61fe4988 | 680 | * Check for required params. |
6a488035 | 681 | * |
cf470720 TO |
682 | * @param array $params |
683 | * Associated array of fields. | |
10114f2d EM |
684 | * |
685 | * @throws API_Exception | |
686 | * @throws Exception | |
a6c01b45 | 687 | * @return array |
72b3a70c | 688 | * array with errors |
6a488035 TO |
689 | */ |
690 | function _civicrm_api3_activity_check_params(&$params) { | |
cf8f0fff | 691 | $activityIds = [ |
7cdbcb16 TO |
692 | 'activity' => CRM_Utils_Array::value('id', $params), |
693 | 'parent' => CRM_Utils_Array::value('parent_id', $params), | |
694 | 'original' => CRM_Utils_Array::value('original_id', $params), | |
cf8f0fff | 695 | ]; |
6a488035 TO |
696 | |
697 | foreach ($activityIds as $id => $value) { | |
698 | if ($value && | |
699 | !CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $value, 'id') | |
700 | ) { | |
10114f2d | 701 | throw new API_Exception('Invalid ' . ucfirst($id) . ' Id'); |
6a488035 TO |
702 | } |
703 | } | |
704 | // this should be handled by wrapper layer & probably the api would already manage it | |
705 | //correctly by doing pseudoconstant validation | |
706 | // needs testing | |
a60ed840 | 707 | $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate'); |
7cdbcb16 TO |
708 | $activityName = CRM_Utils_Array::value('activity_name', $params); |
709 | $activityName = ucfirst($activityName); | |
6a488035 TO |
710 | $activityLabel = CRM_Utils_Array::value('activity_label', $params); |
711 | if ($activityLabel) { | |
a60ed840 | 712 | $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'create'); |
6a488035 TO |
713 | } |
714 | ||
715 | $activityTypeId = CRM_Utils_Array::value('activity_type_id', $params); | |
716 | ||
717 | if ($activityName || $activityLabel) { | |
718 | $activityTypeIdInList = array_search(($activityName ? $activityName : $activityLabel), $activityTypes); | |
719 | ||
720 | if (!$activityTypeIdInList) { | |
35671d00 | 721 | $errorString = $activityName ? "Invalid Activity Name : $activityName" : "Invalid Activity Type Label"; |
6a488035 TO |
722 | throw new Exception($errorString); |
723 | } | |
724 | elseif ($activityTypeId && ($activityTypeId != $activityTypeIdInList)) { | |
10114f2d | 725 | throw new API_Exception('Mismatch in Activity'); |
6a488035 TO |
726 | } |
727 | $params['activity_type_id'] = $activityTypeIdInList; | |
728 | } | |
729 | elseif ($activityTypeId && | |
730 | !array_key_exists($activityTypeId, $activityTypes) | |
731 | ) { | |
10114f2d | 732 | throw new API_Exception('Invalid Activity Type ID'); |
6a488035 TO |
733 | } |
734 | ||
6a488035 TO |
735 | // check for activity duration minutes |
736 | // this should be validated @ the wrapper layer not here | |
737 | // needs testing | |
738 | if (isset($params['duration_minutes']) && !is_numeric($params['duration_minutes'])) { | |
10114f2d | 739 | throw new API_Exception('Invalid Activity Duration (in minutes)'); |
6a488035 TO |
740 | } |
741 | ||
6a488035 TO |
742 | //if adding a new activity & date_time not set make it now |
743 | // this should be managed by the wrapper layer & setting ['api.default'] in speces | |
744 | // needs testing | |
8cc574cf | 745 | if (empty($params['id']) && empty($params['activity_date_time'])) { |
6a488035 TO |
746 | $params['activity_date_time'] = CRM_Utils_Date::processDate(date('Y-m-d H:i:s')); |
747 | } | |
748 | ||
749 | return NULL; | |
750 | } | |
751 | ||
dabf9814 | 752 | /** |
61fe4988 EM |
753 | * Get parameters for activity list. |
754 | * | |
7cdbcb16 | 755 | * @see _civicrm_api3_generic_getlist_params |
dabf9814 | 756 | * |
7cdbcb16 TO |
757 | * @param array $request |
758 | * API request. | |
dabf9814 CW |
759 | */ |
760 | function _civicrm_api3_activity_getlist_params(&$request) { | |
cf8f0fff | 761 | $fieldsToReturn = [ |
7cdbcb16 TO |
762 | 'activity_date_time', |
763 | 'activity_type_id', | |
764 | 'subject', | |
765 | 'source_contact_id', | |
cf8f0fff | 766 | ]; |
dabf9814 CW |
767 | $request['params']['return'] = array_unique(array_merge($fieldsToReturn, $request['extra'])); |
768 | $request['params']['options']['sort'] = 'activity_date_time DESC'; | |
cf8f0fff | 769 | $request['params'] += [ |
dabf9814 CW |
770 | 'is_current_revision' => 1, |
771 | 'is_deleted' => 0, | |
cf8f0fff | 772 | ]; |
dabf9814 CW |
773 | } |
774 | ||
775 | /** | |
61fe4988 EM |
776 | * Get output for activity list. |
777 | * | |
dabf9814 CW |
778 | * @see _civicrm_api3_generic_getlist_output |
779 | * | |
8c6b335b CW |
780 | * @param array $result |
781 | * @param array $request | |
dabf9814 CW |
782 | * |
783 | * @return array | |
784 | */ | |
785 | function _civicrm_api3_activity_getlist_output($result, $request) { | |
cf8f0fff | 786 | $output = []; |
dabf9814 CW |
787 | if (!empty($result['values'])) { |
788 | foreach ($result['values'] as $row) { | |
cf8f0fff | 789 | $data = [ |
dabf9814 CW |
790 | 'id' => $row[$request['id_field']], |
791 | 'label' => $row[$request['label_field']] ? $row[$request['label_field']] : ts('(no subject)'), | |
cf8f0fff | 792 | 'description' => [ |
4a413eb6 | 793 | CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $row['activity_type_id']), |
cf8f0fff CW |
794 | ], |
795 | ]; | |
dabf9814 CW |
796 | if (!empty($row['activity_date_time'])) { |
797 | $data['description'][0] .= ': ' . CRM_Utils_Date::customFormat($row['activity_date_time']); | |
798 | } | |
799 | if (!empty($row['source_contact_id'])) { | |
cf8f0fff | 800 | $data['description'][] = ts('By %1', [ |
7cdbcb16 | 801 | 1 => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $row['source_contact_id'], 'display_name'), |
cf8f0fff | 802 | ]); |
dabf9814 | 803 | } |
8a938c69 CW |
804 | // Add repeating info |
805 | $repeat = CRM_Core_BAO_RecurringEntity::getPositionAndCount($row['id'], 'civicrm_activity'); | |
806 | $data['extra']['is_recur'] = FALSE; | |
807 | if ($repeat) { | |
cf8f0fff | 808 | $data['suffix'] = ts('(%1 of %2)', [1 => $repeat[0], 2 => $repeat[1]]); |
8a938c69 CW |
809 | $data['extra']['is_recur'] = TRUE; |
810 | } | |
dabf9814 CW |
811 | $output[] = $data; |
812 | } | |
813 | } | |
814 | return $output; | |
815 | } |