3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Activity_BAO_Query
{
20 * Build select for Case.
22 * @param CRM_Contact_BAO_Query $query
24 public static function select(&$query) {
25 if (!empty($query->_returnProperties
['activity_id'])) {
26 $query->_select
['activity_id'] = 'civicrm_activity.id as activity_id';
27 $query->_element
['activity_id'] = 1;
28 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
31 if (!empty($query->_returnProperties
['activity_type_id'])) {
32 $query->_select
['activity_type_id'] = 'activity_type.value as activity_type_id';
33 $query->_element
['activity_type_id'] = 1;
34 $query->_tables
['civicrm_activity'] = 1;
35 $query->_tables
['activity_type'] = 1;
36 $query->_whereTables
['civicrm_activity'] = 1;
37 $query->_whereTables
['activity_type'] = 1;
40 if (!empty($query->_returnProperties
['activity_type'])) {
41 $query->_select
['activity_type'] = 'activity_type.label as activity_type';
42 $query->_element
['activity_type'] = 1;
43 $query->_tables
['civicrm_activity'] = 1;
44 $query->_tables
['activity_type'] = 1;
45 $query->_whereTables
['civicrm_activity'] = 1;
46 $query->_whereTables
['activity_type'] = 1;
49 if (!empty($query->_returnProperties
['activity_subject'])) {
50 $query->_select
['activity_subject'] = 'civicrm_activity.subject as activity_subject';
51 $query->_element
['activity_subject'] = 1;
52 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
55 if (!empty($query->_returnProperties
['activity_date_time'])) {
56 $query->_select
['activity_date_time'] = 'civicrm_activity.activity_date_time as activity_date_time';
57 $query->_element
['activity_date_time'] = 1;
58 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
61 if (!empty($query->_returnProperties
['activity_status_id'])) {
62 $query->_select
['activity_status_id'] = 'civicrm_activity.status_id as activity_status_id';
63 $query->_element
['activity_status_id'] = 1;
64 $query->_tables
['civicrm_activity'] = 1;
65 $query->_whereTables
['civicrm_activity'] = 1;
68 if (!empty($query->_returnProperties
['activity_status'])) {
69 $query->_select
['activity_status_id'] = 1;
70 $query->_element
['activity_status'] = 1;
71 $query->_tables
['civicrm_activity'] = 1;
72 $query->_whereTables
['civicrm_activity'] = 1;
75 if (!empty($query->_returnProperties
['activity_duration'])) {
76 $query->_select
['activity_duration'] = 'civicrm_activity.duration as activity_duration';
77 $query->_element
['activity_duration'] = 1;
78 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
81 if (!empty($query->_returnProperties
['activity_location'])) {
82 $query->_select
['activity_location'] = 'civicrm_activity.location as activity_location';
83 $query->_element
['activity_location'] = 1;
84 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
87 if (!empty($query->_returnProperties
['activity_details'])) {
88 $query->_select
['activity_details'] = 'civicrm_activity.details as activity_details';
89 $query->_element
['activity_details'] = 1;
90 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
93 if (!empty($query->_returnProperties
['source_record_id'])) {
94 $query->_select
['source_record_id'] = 'civicrm_activity.source_record_id as source_record_id';
95 $query->_element
['source_record_id'] = 1;
96 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
99 if (!empty($query->_returnProperties
['activity_is_test'])) {
100 $query->_select
['activity_is_test'] = 'civicrm_activity.is_test as activity_is_test';
101 $query->_element
['activity_is_test'] = 1;
102 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
105 if (!empty($query->_returnProperties
['activity_campaign_id'])) {
106 $query->_select
['activity_campaign_id'] = 'civicrm_activity.campaign_id as activity_campaign_id';
107 $query->_element
['activity_campaign_id'] = 1;
108 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
111 if (!empty($query->_returnProperties
['activity_engagement_level'])) {
112 $query->_select
['activity_engagement_level'] = 'civicrm_activity.engagement_level as activity_engagement_level';
113 $query->_element
['activity_engagement_level'] = 1;
114 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
117 if (!empty($query->_returnProperties
['source_contact'])) {
118 $query->_select
['source_contact'] = 'source_contact.sort_name as source_contact';
119 $query->_element
['source_contact'] = 1;
120 $query->_tables
['civicrm_activity'] = $query->_tables
['source_contact'] = $query->_whereTables
['source_contact'] = 1;
123 if (!empty($query->_returnProperties
['source_contact_id'])) {
124 $query->_select
['source_contact_id'] = 'source_contact.id as source_contact_id';
125 $query->_element
['source_contact_id'] = 1;
126 $query->_tables
['civicrm_activity'] = $query->_tables
['source_contact'] = $query->_whereTables
['source_contact'] = 1;
129 if (!empty($query->_returnProperties
['activity_result'])) {
130 $query->_select
['activity_result'] = 'civicrm_activity.result as activity_result';
131 $query->_element
['result'] = 1;
132 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
135 if (!empty($query->_returnProperties
['parent_id'])) {
136 $query->_tables
['parent_id'] = 1;
137 $query->_whereTables
['parent_id'] = 1;
138 $query->_element
['parent_id'] = 1;
141 if (!empty($query->_returnProperties
['activity_priority'])) {
142 $query->_select
['activity_priority'] = 'activity_priority.label as activity_priority,
143 civicrm_activity.priority_id as priority_id';
144 $query->_element
['activity_priority'] = 1;
145 $query->_tables
['activity_priority'] = 1;
146 $query->_whereTables
['activity_priority'] = 1;
147 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
152 * Given a list of conditions in query generate the required where clause.
154 * @param \CRM_Contact_BAO_Query $query
156 * @throws \CRM_Core_Exception
158 public static function where(&$query) {
159 foreach (array_keys($query->_params
) as $id) {
160 if (substr($query->_params
[$id][0], 0, 9) === 'activity_') {
161 if ($query->_mode
== CRM_Contact_BAO_Query
::MODE_CONTACTS
) {
162 $query->_useDistinct
= TRUE;
164 $query->_params
[$id][3];
165 self
::whereClauseSingle($query->_params
[$id], $query);
171 * Where clause for a single field.
173 * @param array $values
174 * @param CRM_Contact_BAO_Query $query
176 * @throws \CRM_Core_Exception
178 public static function whereClauseSingle(&$values, &$query) {
179 list($name, $op, $value, $grouping) = $values;
181 $fields = CRM_Activity_BAO_Activity
::exportableFields();
182 $fieldSpec = $query->getFieldSpec($name);
184 $query->_tables
['civicrm_activity'] = $query->_whereTables
['civicrm_activity'] = 1;
185 if ($query->_mode
& CRM_Contact_BAO_Query
::MODE_ACTIVITY
) {
186 $query->_skipDeleteClause
= TRUE;
190 case 'activity_type':
191 case 'activity_type_id':
192 case 'activity_status':
193 case 'activity_status_id':
194 case 'activity_engagement_level':
196 case 'activity_campaign_id':
197 case 'activity_priority_id':
198 // We no longer expect "subject" as a specific criteria (as of CRM-19447),
199 // but we still use activity_subject in Activity.Get API
200 case 'activity_subject':
201 $query->_where
[$grouping][] = CRM_Contact_BAO_Query
::buildClause($fieldSpec['where'], $op, $value, CRM_Utils_Type
::typeToString($fieldSpec['type']));
202 $query->_qill
[$grouping][] = $query->getQillForField($fieldSpec['is_pseudofield_for'] ??
$fieldSpec['name'], $value, $op, $fieldSpec);
205 case 'activity_text':
206 self
::whereClauseSingleActivityText($values, $query);
209 case 'activity_priority':
210 $query->_where
[$grouping][] = CRM_Contact_BAO_Query
::buildClause("$name.label", $op, $value, 'String');
211 list($op, $value) = CRM_Contact_BAO_Query
::buildQillForFieldValue('CRM_Activity_DAO_Activity', $name, $value, $op);
212 $query->_qill
[$grouping][] = ts('%1 %2 %3', [
213 1 => $fields[$name]['title'],
217 $query->_tables
[$name] = $query->_whereTables
[$name] = 1;
220 case 'activity_survey_id':
224 $value = CRM_Utils_Type
::escape($value, 'Integer');
225 $query->_where
[$grouping][] = " civicrm_activity.source_record_id = $value";
226 $query->_qill
[$grouping][] = ts('Survey') . ' - ' . CRM_Core_DAO
::getFieldValue('CRM_Campaign_DAO_Survey', $value, 'title');
229 case 'activity_role':
230 CRM_Contact_BAO_Query
::$_activityRole = $values[2];
231 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
232 $sourceID = CRM_Utils_Array
::key('Activity Source', $activityContacts);
233 $assigneeID = CRM_Utils_Array
::key('Activity Assignees', $activityContacts);
234 $targetID = CRM_Utils_Array
::key('Activity Targets', $activityContacts);
237 $query->_tables
['civicrm_activity_contact'] = $query->_whereTables
['civicrm_activity_contact'] = 1;
238 if ($values[2] == 1) {
239 $query->_where
[$grouping][] = " civicrm_activity_contact.record_type_id = $sourceID";
240 $query->_qill
[$grouping][] = ts('Activity created by');
242 elseif ($values[2] == 2) {
243 $query->_where
[$grouping][] = " civicrm_activity_contact.record_type_id = $assigneeID";
244 $query->_qill
[$grouping][] = ts('Activity assigned to');
246 elseif ($values[2] == 3) {
247 $query->_where
[$grouping][] = " civicrm_activity_contact.record_type_id = $targetID";
248 $query->_qill
[$grouping][] = ts('Activity targeted to');
253 case 'activity_test':
254 // We don't want to include all tests for sql OR CRM-7827
255 if (!$value ||
$query->getOperator() != 'OR') {
256 $query->_where
[$grouping][] = CRM_Contact_BAO_Query
::buildClause("civicrm_activity.is_test", $op, $value, "Boolean");
258 $query->_qill
[$grouping][] = ts('Activity is a Test');
263 case 'activity_date':
264 case 'activity_date_low':
265 case 'activity_date_high':
266 case 'activity_date_time_low':
267 case 'activity_date_time_high':
268 $query->dateQueryBuilder($values,
269 'civicrm_activity', str_replace([
272 ], '', $name), 'activity_date_time', ts('Activity Date')
276 case 'activity_taglist':
279 foreach ($taglist as $val) {
281 $val = explode(',', $val);
282 foreach ($val as $tId) {
283 if (is_numeric($tId)) {
290 case 'activity_tags':
291 $activityTags = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]);
293 if (!is_array($value)) {
294 $value = explode(',', $value);
298 foreach ($value as $k => $v) {
299 $names[] = $activityTags[$v];
302 $query->_where
[$grouping][] = "civicrm_activity_tag.tag_id IN (" . implode(",", $value) . ")";
303 $query->_qill
[$grouping][] = ts('Activity Tag %1', [1 => $op]) . ' ' . implode(' ' . ts('OR') . ' ', $names);
304 $query->_tables
['civicrm_activity_tag'] = $query->_whereTables
['civicrm_activity_tag'] = 1;
307 case 'activity_result':
308 if (is_array($value)) {
310 foreach ($value as $id => $k) {
311 $safe[] = "'" . CRM_Utils_Type
::escape($k, 'String') . "'";
313 $query->_where
[$grouping][] = "civicrm_activity.result IN (" . implode(',', $safe) . ")";
314 $query->_qill
[$grouping][] = ts("Activity Result - %1", [1 => implode(' or ', $safe)]);
320 $query->_where
[$grouping][] = "civicrm_activity.parent_id IS NOT NULL";
321 $query->_qill
[$grouping][] = ts('Activities which have Followup Activities');
323 elseif ($value == 2) {
324 $query->_where
[$grouping][] = "civicrm_activity.parent_id IS NULL";
325 $query->_qill
[$grouping][] = ts('Activities without Followup Activities');
329 case 'followup_parent_id':
331 $query->_where
[$grouping][] = "civicrm_activity.parent_id IS NOT NULL";
332 $query->_qill
[$grouping][] = ts('Activities which are Followup Activities');
334 elseif ($value == 2) {
335 $query->_where
[$grouping][] = "civicrm_activity.parent_id IS NULL";
336 $query->_qill
[$grouping][] = ts('Activities which are not Followup Activities');
340 case 'source_contact':
341 case 'source_contact_id':
342 $columnName = strstr($name, '_id') ?
'id' : 'sort_name';
343 $query->_where
[$grouping][] = CRM_Contact_BAO_Query
::buildClause("source_contact.{$columnName}", $op, $value, CRM_Utils_Type
::typeToString($fields[$name]['type']));
344 list($op, $value) = CRM_Contact_BAO_Query
::buildQillForFieldValue('CRM_Contact_DAO_Contact', $columnName, $value, $op);
345 $query->_qill
[$grouping][] = ts('%1 %2 %3', [
346 1 => $fields[$name]['title'],
355 * @param string $name
359 * @return null|string
361 public static function from($name, $mode, $side) {
364 case 'civicrm_activity':
365 //CRM-7480 we are going to civicrm_activity table either
366 //from civicrm_activity_target or civicrm_activity_assignment.
367 //as component specific activities does not have entry in
368 //activity target table so lets consider civicrm_activity_assignment.
369 $from .= " $side JOIN civicrm_activity_contact
370 ON ( civicrm_activity_contact.contact_id = contact_a.id ) ";
371 $from .= " $side JOIN civicrm_activity
372 ON ( civicrm_activity.id = civicrm_activity_contact.activity_id
373 AND civicrm_activity.is_deleted = 0 AND civicrm_activity.is_current_revision = 1 )";
374 // Do not show deleted contact's activity
375 $from .= " INNER JOIN civicrm_contact
376 ON ( civicrm_activity_contact.contact_id = civicrm_contact.id and civicrm_contact.is_deleted != 1 )";
379 case 'activity_type':
380 $from .= " $side JOIN civicrm_option_group option_group_activity_type ON (option_group_activity_type.name = 'activity_type')";
381 $from .= " $side JOIN civicrm_option_value activity_type ON (civicrm_activity.activity_type_id = activity_type.value
382 AND option_group_activity_type.id = activity_type.option_group_id ) ";
385 case 'activity_priority':
386 $from .= " $side JOIN civicrm_option_group option_group_activity_priority ON (option_group_activity_priority.name = 'priority')";
387 $from .= " $side JOIN civicrm_option_value activity_priority ON (civicrm_activity.priority_id = activity_priority.value
388 AND option_group_activity_priority.id = activity_priority.option_group_id ) ";
391 case 'civicrm_activity_tag':
392 $from .= " $side JOIN civicrm_entity_tag as civicrm_activity_tag ON ( civicrm_activity_tag.entity_table = 'civicrm_activity' AND civicrm_activity_tag.entity_id = civicrm_activity.id ) ";
395 case 'source_contact':
396 $sourceID = CRM_Core_PseudoConstant
::getKey(
397 'CRM_Activity_BAO_ActivityContact',
402 LEFT JOIN civicrm_activity_contact source_activity
403 ON (source_activity.activity_id = civicrm_activity_contact.activity_id
404 AND source_activity.record_type_id = {$sourceID})
405 LEFT JOIN civicrm_contact source_contact ON (source_activity.contact_id = source_contact.id)";
409 $from = "$side JOIN civicrm_activity AS parent_id ON civicrm_activity.id = parent_id.parent_id";
417 * Get the metadata for fields to be included on the activity search form.
419 * @throws \CiviCRM_API3_Exception
420 * @todo ideally this would be a trait included on the activity search & advanced search
421 * rather than a static function.
423 public static function getSearchFieldMetadata() {
424 $fields = ['activity_type_id', 'activity_date_time', 'priority_id', 'activity_location', 'activity_status_id'];
425 $metadata = civicrm_api3('Activity', 'getfields', [])['values'];
426 $metadata = array_intersect_key($metadata, array_flip($fields));
427 $metadata['activity_text'] = [
428 'title' => ts('Activity Text'),
429 'type' => CRM_Utils_Type
::T_STRING
,
430 'is_pseudofield' => TRUE,
439 * Add all the elements shared between case activity search and advanced search.
441 * @param CRM_Core_Form_Search $form
443 * @throws \CiviCRM_API3_Exception
444 * @throws \CRM_Core_Exception
446 public static function buildSearchForm(&$form) {
447 $form->addSearchFieldMetadata(['Activity' => self
::getSearchFieldMetadata()]);
448 $form->addFormFieldsFromMetadata();
450 $followUpActivity = [
454 $form->addRadio('parent_id', NULL, $followUpActivity, ['allowClear' => TRUE]);
455 $form->addRadio('followup_parent_id', NULL, $followUpActivity, ['allowClear' => TRUE]);
458 2 => ts('Assigned to'),
461 $form->addRadio('activity_role', NULL, $activityRoles, ['allowClear' => TRUE]);
462 $form->setDefaults(['activity_role' => 3]);
463 $activityStatus = CRM_Core_PseudoConstant
::get('CRM_Activity_DAO_Activity', 'status_id', [
465 'labelColumn' => 'name',
467 $ssID = $form->get('ssID');
468 $status = [$activityStatus['Completed'], $activityStatus['Scheduled']];
469 //If status is saved in smart group.
470 if (!empty($ssID) && !empty($form->_formValues
['activity_status_id'])) {
471 $status = $form->_formValues
['activity_status_id'];
473 $form->setDefaults(['activity_status_id' => $status]);
475 $form->addElement('text', 'activity_text', ts('Activity Text'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
477 $form->addRadio('activity_option', '', CRM_Core_SelectValues
::activityTextOptions());
478 $form->setDefaults(['activity_option' => 'both']);
480 $form->addYesNo('activity_test', ts('Activity is a Test?'));
481 $activity_tags = CRM_Core_BAO_Tag
::getColorTags('civicrm_activity');
483 if ($activity_tags) {
484 $form->add('select2', 'activity_tags', ts('Activity Tag(s)'),
485 $activity_tags, FALSE, [
486 'id' => 'activity_tags',
489 'class' => 'crm-select2',
490 'placeholder' => ts('- select -'),
495 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_activity');
496 CRM_Core_Form_Tag
::buildQuickForm($form, $parentNames, 'civicrm_activity', NULL, TRUE, TRUE);
498 $surveys = CRM_Campaign_BAO_Survey
::getSurveys(TRUE, FALSE, FALSE, TRUE);
500 $form->add('select', 'activity_survey_id', ts('Survey / Petition'),
501 ['' => ts('- none -')] +
$surveys, FALSE,
502 ['class' => 'crm-select2']
506 CRM_Core_BAO_Query
::addCustomFormFields($form, ['Activity']);
508 CRM_Campaign_BAO_Campaign
::addCampaignInComponentSearch($form, 'activity_campaign_id');
510 // Add engagement level CRM-7775.
511 $buildEngagementLevel = FALSE;
512 $buildSurveyResult = FALSE;
513 if (CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
514 CRM_Campaign_BAO_Campaign
::accessCampaign()
516 $buildEngagementLevel = TRUE;
517 $form->addSelect('activity_engagement_level', [
518 'entity' => 'activity',
519 'context' => 'search',
522 // Add survey result field.
523 $optionGroups = CRM_Campaign_BAO_Survey
::getResultSets('name');
525 foreach ($optionGroups as $gid => $name) {
527 $value = CRM_Core_OptionGroup
::values($name);
528 if (!empty($value)) {
529 foreach ($value as $k => $v) {
530 $resultOptions[$v] = $v;
535 // If no survey result options have been created, don't build
536 // the field to avoid clutter.
537 if (count($resultOptions) > 0) {
538 $buildSurveyResult = TRUE;
539 asort($resultOptions);
540 $form->add('select', 'activity_result', ts("Survey Result"),
541 $resultOptions, FALSE,
543 'id' => 'activity_result',
544 'multiple' => 'multiple',
545 'class' => 'crm-select2',
551 $form->assign('buildEngagementLevel', $buildEngagementLevel);
552 $form->assign('buildSurveyResult', $buildSurveyResult);
553 $form->setDefaults(['activity_test' => 0]);
558 * @param bool $includeCustomFields
562 public static function defaultReturnProperties($mode, $includeCustomFields = TRUE) {
564 if ($mode & CRM_Contact_BAO_Query
::MODE_ACTIVITY
) {
568 'contact_sub_type' => 1,
571 'activity_type' => 1,
572 'activity_type_id' => 1,
573 'activity_subject' => 1,
574 'activity_date_time' => 1,
575 'activity_duration' => 1,
576 'activity_location' => 1,
577 'activity_details' => 1,
578 'activity_status' => 1,
579 'activity_priority' => 1,
580 'source_contact' => 1,
581 'source_record_id' => 1,
582 'activity_is_test' => 1,
583 'activity_campaign_id' => 1,
585 'activity_engagement_level' => 1,
589 if ($includeCustomFields) {
590 // also get all the custom activity properties
591 $fields = CRM_Core_BAO_CustomField
::getFieldsForImport('Activity');
592 if (!empty($fields)) {
593 foreach ($fields as $name => $dontCare) {
594 $properties[$name] = 1;
604 * Get the list of fields required to populate the selector.
606 * The default return properties array returns far too many fields for 'everyday use. Every field you add to this array
607 * kills a small kitten so add carefully.
609 public static function selectorReturnProperties() {
613 'contact_sub_type' => 1,
616 'activity_type_id' => 1,
617 'activity_subject' => 1,
618 'activity_date_time' => 1,
619 'activity_status_id' => 1,
620 'source_contact' => 1,
621 'source_record_id' => 1,
622 'activity_is_test' => 1,
623 'activity_campaign_id' => 1,
624 'activity_engagement_level' => 1,
631 * Where/qill clause for notes
633 * @param array $values
634 * @param CRM_Contact_BAO_Query $query
636 * @throws \CRM_Core_Exception
638 public static function whereClauseSingleActivityText(&$values, &$query) {
639 list($name, $op, $value, $grouping, $wildcard) = $values;
640 $activityOptionValues = $query->getWhereValues('activity_option', $grouping);
641 $activityOption = CRM_Utils_Array
::value(2, $activityOptionValues, 6);
643 $query->_useDistinct
= TRUE;
645 $label = ts('Activity Text (%1)', [1 => CRM_Utils_Array
::value($activityOption, CRM_Core_SelectValues
::activityTextOptions())]);
647 if ($activityOption %
2 == 0) {
648 $clauses[] = $query->buildClause('civicrm_activity.details', $op, $value, 'String');
650 if ($activityOption %
3 == 0) {
651 $clauses[] = $query->buildClause('civicrm_activity.subject', $op, $value, 'String');
654 $query->_where
[$grouping][] = "( " . implode(' OR ', $clauses) . " )";
655 list($qillOp, $qillVal) = $query->buildQillForFieldValue(NULL, $name, $value, $op);
656 $query->_qill
[$grouping][] = ts("%1 %2 '%3'", [