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_Contact_Form_Search_Criteria
{
20 * @param CRM_Contact_Form_Search_Advanced $form
22 * @throws \CRM_Core_Exception
23 * @throws \CiviCRM_API3_Exception
25 public static function basic(&$form) {
26 $form->addSearchFieldMetadata(['Contact' => self
::getFilteredSearchFieldMetadata('basic')]);
27 $form->addFormFieldsFromMetadata();
28 self
::setBasicSearchFields($form);
29 $form->addElement('hidden', 'hidden_basic', 1);
31 if ($form->_searchOptions
['contactType']) {
32 $contactTypes = CRM_Contact_BAO_ContactType
::getSelectElements();
35 $form->add('select', 'contact_type', ts('Contact Type(s)'), $contactTypes, FALSE,
36 ['id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;']
41 if ($form->_searchOptions
['groups']) {
42 // multiselect for groups
44 // Arrange groups into hierarchical listing (child groups follow their parents and have indentation spacing in title)
45 $groupHierarchy = CRM_Contact_BAO_Group
::getGroupsHierarchy($form->_group
, NULL, ' ', TRUE);
47 $form->add('select', 'group', ts('Groups'), $groupHierarchy, FALSE,
48 ['id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2']
50 $groupOptions = CRM_Core_BAO_OptionValue
::getOptionValuesAssocArrayFromName('group_type');
51 $form->add('select', 'group_type', ts('Group Types'), $groupOptions, FALSE,
52 ['id' => 'group_type', 'multiple' => 'multiple', 'class' => 'crm-select2']
54 $form->add('hidden', 'group_search_selected', 'group');
58 if ($form->_searchOptions
['tags']) {
59 // multiselect for categories
60 $contactTags = CRM_Core_BAO_Tag
::getTags();
63 $form->add('select', 'contact_tags', ts('Select Tag(s)'), $contactTags, FALSE,
64 ['id' => 'contact_tags', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;']
68 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_contact');
69 CRM_Core_Form_Tag
::buildQuickForm($form, $parentNames, 'civicrm_contact', NULL, TRUE, FALSE);
71 $used_for = CRM_Core_OptionGroup
::values('tag_used_for');
73 $showAllTagTypes = FALSE;
74 foreach ($used_for as $key => $value) {
75 //check tags for every type and find if there are any defined
76 $tags = CRM_Core_BAO_Tag
::getTagsUsedFor($key, FALSE, TRUE, NULL);
77 // check if there are tags other than contact type, if no - keep checkbox hidden on adv search
78 // we will hide searching contact by attachments tags until it will be implemented in core
79 if (count($tags) && $key != 'civicrm_file' && $key != 'civicrm_contact') {
80 //if tags exists then add type to display in adv search form help text
81 $tagsTypes[] = $value;
82 $showAllTagTypes = TRUE;
85 $tagTypesText = implode(" or ", $tagsTypes);
86 if ($showAllTagTypes) {
87 $form->add('checkbox', 'all_tag_types', ts('Include tags used for %1', [1 => $tagTypesText]));
88 $form->add('hidden', 'tag_types_text', $tagTypesText);
92 //added contact source
93 $form->add('text', 'contact_source', ts('Contact Source'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'contact_source'));
96 $form->addElement('text', 'job_title', ts('Job Title'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'job_title'));
99 $form->add('number', 'contact_id', ts('Contact ID'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'id') +
['min' => 1]);
100 $form->addRule('contact_id', ts('Please enter valid Contact ID'), 'positiveInteger');
103 $form->addElement('text', 'external_identifier', ts('External ID'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'external_identifier'));
105 if (CRM_Core_Permission
::check('access deleted contacts') and Civi
::settings()->get('contact_undelete')) {
106 $form->add('checkbox', 'deleted_contacts', ts('Search in Trash') . '<br />' . ts('(deleted contacts)'));
109 // add checkbox for cms users only
110 $form->addYesNo('uf_user', ts('CMS User?'), TRUE);
113 $form->add('text', 'tag_search', ts('All Tags'));
115 // add search profiles
117 // FIXME: This is probably a part of profiles - need to be
118 // FIXME: eradicated from here when profiles are reworked.
119 $types = ['Participant', 'Contribution', 'Membership'];
121 // get component profiles
122 $componentProfiles = [];
123 $componentProfiles = CRM_Core_BAO_UFGroup
::getProfiles($types);
125 $ufGroups = CRM_Core_BAO_UFGroup
::getModuleUFGroup('Search Profile', 1);
126 $accessibleUfGroups = CRM_Core_Permission
::ufGroup(CRM_Core_Permission
::VIEW
);
128 $searchProfiles = [];
129 foreach ($ufGroups as $key => $var) {
130 if (!array_key_exists($key, $componentProfiles) && in_array($key, $accessibleUfGroups)) {
131 $searchProfiles[$key] = $var['title'];
137 ts('Views For Display Contacts'),
139 '0' => ts('- default view -'),
142 ['class' => 'crm-select2']
145 $componentModes = CRM_Contact_Form_Search
::getModeSelect();
146 $form->assign('component_mappings', json_encode(CRM_Contact_Form_Search
::getModeToComponentMapping()));
147 if (count($componentModes) > 1) {
150 ts('Display Results As'),
153 ['class' => 'crm-select2']
159 ts('Search Operator'),
161 CRM_Contact_BAO_Query
::SEARCH_OPERATOR_AND
=> ts('AND'),
162 CRM_Contact_BAO_Query
::SEARCH_OPERATOR_OR
=> ts('OR'),
164 ['allowClear' => FALSE]
167 // add the option to display relationships
168 $rTypes = CRM_Core_PseudoConstant
::relationshipType();
169 $rSelect = ['' => ts('- Select Relationship Type-')];
170 foreach ($rTypes as $rid => $rValue) {
171 if ($rValue['label_a_b'] == $rValue['label_b_a']) {
172 $rSelect[$rid] = $rValue['label_a_b'];
175 $rSelect["{$rid}_a_b"] = $rValue['label_a_b'];
176 $rSelect["{$rid}_b_a"] = $rValue['label_b_a'];
180 $form->addElement('select',
181 'display_relationship_type',
182 ts('Display Results as Relationship'),
184 ['class' => 'crm-select2']
187 // checkboxes for DO NOT phone, email, mail
188 // we take labels from SelectValues
189 $t = CRM_Core_SelectValues
::privacy();
196 'id' => 'privacy_options',
197 'multiple' => 'multiple',
198 'class' => 'crm-select2',
202 $form->addElement('select',
213 2 => ts('Include by Privacy Option(s)'),
215 $form->addRadio('privacy_toggle', ts('Privacy Options'), $options, ['allowClear' => FALSE]);
217 // preferred communication method
218 if (Civi
::settings()->get('civimail_multiple_bulk_emails')) {
219 $form->addSelect('email_on_hold',
220 ['entity' => 'email', 'multiple' => 'multiple', 'label' => ts('Email On Hold'), 'options' => CRM_Core_PseudoConstant
::emailOnHoldOptions()]);
223 $form->add('advcheckbox', 'email_on_hold', ts('Email On Hold'));
226 $form->addSelect('preferred_communication_method',
227 ['entity' => 'contact', 'multiple' => 'multiple', 'label' => ts('Preferred Communication Method'), 'option_url' => NULL, 'placeholder' => ts('- any -')]);
229 //CRM-6138 Preferred Language
230 $form->addSelect('preferred_language', ['class' => 'twenty', 'context' => 'search']);
233 $form->addElement('text', 'phone_numeric', ts('Phone'), CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone', 'phone'));
234 $locationType = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Address', 'location_type_id');
235 $phoneType = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Phone', 'phone_type_id');
236 $form->add('select', 'phone_location_type_id', ts('Phone Location'), ['' => ts('- any -')] +
$locationType, FALSE, ['class' => 'crm-select2']);
237 $form->add('select', 'phone_phone_type_id', ts('Phone Type'), ['' => ts('- any -')] +
$phoneType, FALSE, ['class' => 'crm-select2']);
241 * Get the metadata for fields to be included on the contact search form.
243 * @throws \CiviCRM_API3_Exception
245 public static function getSearchFieldMetadata() {
248 'title' => ts('Complete OR Partial Name'),
249 'template_grouping' => 'basic',
250 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/sort_name.tpl',
252 'first_name' => ['template_grouping' => 'basic'],
253 'last_name' => ['template_grouping' => 'basic'],
254 'email' => ['title' => ts('Complete OR Partial Email'), 'entity' => 'Email', 'template_grouping' => 'basic'],
255 'contact_tags' => ['name' => 'contact_tags', 'type' => CRM_Utils_Type
::T_INT
, 'is_pseudofield' => TRUE, 'template_grouping' => 'basic'],
256 'created_date' => ['name' => 'created_date', 'template_grouping' => 'changeLog'],
257 'modified_date' => ['name' => 'modified_date', 'template_grouping' => 'changeLog'],
258 'birth_date' => ['name' => 'birth_date', 'template_grouping' => 'demographic'],
259 'deceased_date' => ['name' => 'deceased_date', 'template_grouping' => 'demographic'],
260 'is_deceased' => ['is_deceased', 'template_grouping' => 'demographic'],
261 'relationship_start_date' => ['name' => 'relationship_start_date', 'template_grouping' => 'relationship'],
262 'relationship_end_date' => ['name' => 'relationship_end_date', 'template_grouping' => 'relationship'],
263 // PseudoRelationship date field.
264 'relation_active_period_date' => [
265 'name' => 'relation_active_period_date',
266 'type' => CRM_Utils_Type
::T_DATE + CRM_Utils_Type
::T_TIME
,
267 'title' => ts('Active Period'),
268 'table_name' => 'civicrm_relationship',
269 'where' => 'civicrm_relationship.start_date',
270 'where_end' => 'civicrm_relationship.end_date',
271 'html' => ['type' => 'SelectDate', 'formatType' => 'activityDateTime'],
272 'template_grouping' => 'relationship',
276 $metadata = civicrm_api3('Relationship', 'getfields', [])['values'];
277 $metadata = array_merge($metadata, civicrm_api3('Contact', 'getfields', [])['values']);
278 foreach ($fields as $fieldName => $field) {
279 $fields[$fieldName] = array_merge(CRM_Utils_Array
::value($fieldName, $metadata, []), $field);
285 * Get search field metadata filtered by the template grouping field.
287 * @param string $filter
290 * @throws \CiviCRM_API3_Exception
292 public static function getFilteredSearchFieldMetadata($filter) {
293 $fields = self
::getSearchFieldMetadata();
294 foreach ($fields as $index => $field) {
295 if ($field['template_grouping'] !== $filter) {
296 unset($fields[$index]);
303 * Defines the fields that can be displayed for the basic search section.
305 * @param CRM_Core_Form $form
307 * @throws \CiviCRM_API3_Exception
309 protected static function setBasicSearchFields($form) {
311 foreach (self
::getFilteredSearchFieldMetadata('basic') as $fieldName => $field) {
312 $searchFields[$fieldName] = $field;
314 $form->assign('basicSearchFields', array_merge(self
::getBasicSearchFields(), $searchFields));
318 * Return list of basic contact fields that can be displayed for the basic search section.
321 public static function getBasicSearchFields() {
322 $userFramework = CRM_Core_Config
::singleton()->userFramework
;
324 // For now an empty array is still left in place for ordering.
328 'email' => ['name' => 'email'],
329 'contact_type' => ['name' => 'contact_type'],
332 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/group.tpl',
334 'contact_tags' => ['name' => 'contact_tags'],
335 'tag_types_text' => ['name' => 'tag_types_text'],
337 'name' => 'tag_search',
338 'help' => ['id' => 'id-all-tags'],
343 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl',
346 'name' => 'all_tag_types',
347 'class' => 'search-field__span-3 search-field__checkbox',
348 'help' => ['id' => 'id-all-tag-types'],
351 'name' => 'phone_numeric',
352 'description' => ts('Punctuation and spaces are ignored.'),
354 'phone_location_type_id' => ['name' => 'phone_location_type_id'],
355 'phone_phone_type_id' => ['name' => 'phone_phone_type_id'],
356 'privacy_toggle' => [
357 'name' => 'privacy_toggle',
358 'class' => 'search-field__span-2',
359 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl',
361 'preferred_communication_method' => [
362 'name' => 'preferred_communication_method',
363 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl',
365 'contact_source' => [
366 'name' => 'contact_source',
367 'help' => ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact'],
369 'job_title' => ['name' => 'job_title'],
370 'preferred_language' => ['name' => 'preferred_language'],
372 'name' => 'contact_id',
373 'help' => ['id' => 'id-contact-id', 'file' => 'CRM/Contact/Form/Contact'],
375 'external_identifier' => [
376 'name' => 'external_identifier',
377 'help' => ['id' => 'id-external-id', 'file' => 'CRM/Contact/Form/Contact'],
381 'description' => ts('Does the contact have a %1 Account?', [$userFramework]),
387 * @param CRM_Core_Form $form
389 public static function location(&$form) {
390 $config = CRM_Core_Config
::singleton();
391 // Build location criteria based on _submitValues if
392 // available; otherwise, use $form->_formValues.
393 $formValues = $form->_submitValues
;
395 if (empty($formValues) && !empty($form->_formValues
)) {
396 $formValues = $form->_formValues
;
399 $form->addElement('hidden', 'hidden_location', 1);
401 $addressOptions = CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
402 'address_options', TRUE, NULL, TRUE
405 $attributes = CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Address');
408 'street_address' => [ts('Street Address'), $attributes['street_address'], NULL, NULL],
409 'supplemental_address_1' => [ts('Supplemental Address 1'), $attributes['supplemental_address_1'], NULL, NULL],
410 'supplemental_address_2' => [ts('Supplemental Address 2'), $attributes['supplemental_address_2'], NULL, NULL],
411 'supplemental_address_3' => [ts('Supplemental Address 3'), $attributes['supplemental_address_3'], NULL, NULL],
412 'city' => [ts('City'), $attributes['city'], NULL, NULL],
413 'postal_code' => [ts('Postal Code'), $attributes['postal_code'], NULL, NULL],
414 'country' => [ts('Country'), $attributes['country_id'], 'country', FALSE],
415 'state_province' => [ts('State/Province'), $attributes['state_province_id'], 'stateProvince', TRUE],
416 'county' => [ts('County'), $attributes['county_id'], 'county', TRUE],
417 'address_name' => [ts('Address Name'), $attributes['address_name'], NULL, NULL],
418 'street_number' => [ts('Street Number'), $attributes['street_number'], NULL, NULL],
419 'street_name' => [ts('Street Name'), $attributes['street_name'], NULL, NULL],
420 'street_unit' => [ts('Apt/Unit/Suite'), $attributes['street_unit'], NULL, NULL],
423 $parseStreetAddress = $addressOptions['street_address_parsing'] ??
0;
424 $form->assign('parseStreetAddress', $parseStreetAddress);
425 foreach ($elements as $name => $v) {
426 list($title, $attributes, $select, $multiSelect) = $v;
429 ['street_number', 'street_name', 'street_unit']
431 if (!$parseStreetAddress) {
435 elseif (!$addressOptions[$name]) {
440 $attributes = $attributes[$name];
444 if ($select == 'stateProvince' ||
$select == 'county') {
445 $element = $form->addChainSelect($name);
448 $selectElements = ['' => ts('- any -')] + CRM_Core_PseudoConstant
::$select();
449 $element = $form->add('select', $name, $title, $selectElements, FALSE, ['class' => 'crm-select2']);
452 $element->setMultiple(TRUE);
456 $form->addElement('text', $name, $title, $attributes);
459 if ($addressOptions['postal_code']) {
460 $attr = ['class' => 'six'] +
(array) CRM_Utils_Array
::value('postal_code', $attributes);
461 $form->addElement('text', 'postal_code_low', NULL, $attr +
['placeholder' => ts('From')]);
462 $form->addElement('text', 'postal_code_high', NULL, $attr +
['placeholder' => ts('To')]);
466 // extend addresses with proximity search
467 if (CRM_Utils_GeocodeProvider
::getUsableClassName()) {
468 $form->addElement('text', 'prox_distance', ts('Find contacts within'), ['class' => 'six']);
469 $form->addElement('select', 'prox_distance_unit', NULL, [
470 'miles' => ts('Miles'),
471 'kilos' => ts('Kilometers'),
473 $form->addRule('prox_distance', ts('Please enter positive number as a distance'), 'numeric');
476 $form->addSelect('world_region', ['entity' => 'address', 'context' => 'search']);
478 // select for location type
479 $locationType = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Address', 'location_type_id');
480 $form->add('select', 'location_type', ts('Address Location'), $locationType, FALSE, [
482 'class' => 'crm-select2',
483 'placeholder' => ts('Primary'),
486 // custom data extending addresses
487 CRM_Core_BAO_Query
::addCustomFormFields($form, ['Address']);
491 * @param CRM_Core_Form $form
493 public static function activity(&$form) {
494 $form->add('hidden', 'hidden_activity', 1);
495 CRM_Activity_BAO_Query
::buildSearchForm($form);
499 * @param CRM_Core_Form $form
501 * @throws \CiviCRM_API3_Exception
503 public static function changeLog(&$form) {
504 $form->add('hidden', 'hidden_changeLog', 1);
505 $form->addSearchFieldMetadata(['Contact' => self
::getFilteredSearchFieldMetadata('changeLog')]);
506 $form->addFormFieldsFromMetadata();
507 // block for change log
508 $form->addElement('text', 'changed_by', ts('Modified By'), NULL);
512 * @param CRM_Core_Form $form
514 public static function task(&$form) {
515 $form->add('hidden', 'hidden_task', 1);
519 * @param CRM_Core_Form_Search $form
521 * @throws \CiviCRM_API3_Exception
523 public static function relationship(&$form) {
524 $form->add('hidden', 'hidden_relationship', 1);
525 $form->addSearchFieldMetadata(['Relationship' => self
::getFilteredSearchFieldMetadata('relationship')]);
526 $form->addFormFieldsFromMetadata();
527 $form->add('text', 'relation_description', ts('Description'), ['class' => 'twenty']);
528 $allRelationshipType = CRM_Contact_BAO_Relationship
::getContactRelationshipType(NULL, NULL, NULL, NULL, TRUE);
529 $form->add('select', 'relation_type_id', ts('Relationship Type'), ['' => ts('- select -')] +
$allRelationshipType, FALSE, ['multiple' => TRUE, 'class' => 'crm-select2']);
530 $form->addElement('text', 'relation_target_name', ts('Target Contact'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
532 $relStatusOption = [ts('Active'), ts('Inactive'), ts('All')];
533 $form->addRadio('relation_status', ts('Relationship Status'), $relStatusOption);
534 $form->setDefaults(['relation_status' => 0]);
535 // relation permission
536 $allRelationshipPermissions = CRM_Contact_BAO_Relationship
::buildOptions('is_permission_a_b');
537 $form->add('select', 'relation_permission', ts('Permissioned Relationship'),
538 ['' => ts('- select -')] +
$allRelationshipPermissions, FALSE, ['multiple' => TRUE, 'class' => 'crm-select2']);
540 //add the target group
542 $form->add('select', 'relation_target_group', ts('Target Contact(s) in Group'), $form->_group
, FALSE,
543 ['id' => 'relation_target_group', 'multiple' => 'multiple', 'class' => 'crm-select2']
547 // add all the custom searchable fields
548 CRM_Core_BAO_Query
::addCustomFormFields($form, ['Relationship']);
552 * @param CRM_Core_Form_Search $form
554 * @throws \CiviCRM_API3_Exception
556 public static function demographics(&$form) {
557 $form->add('hidden', 'hidden_demographics', 1);
558 $form->addSearchFieldMetadata(['Contact' => self
::getFilteredSearchFieldMetadata('demographic')]);
559 $form->addFormFieldsFromMetadata();
560 // radio button for gender
561 $genderOptionsAttributes = [];
562 $gender = CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'gender_id');
563 foreach ($gender as $key => $var) {
564 $genderOptionsAttributes[$key] = ['id' => "civicrm_gender_{$var}_{$key}"];
566 $form->addRadio('gender_id', ts('Gender'), $gender, ['allowClear' => TRUE], NULL, FALSE, $genderOptionsAttributes);
568 $form->add('number', 'age_low', ts('Min Age'), ['class' => 'four', 'min' => 0]);
569 $form->addRule('age_low', ts('Please enter a positive integer'), 'positiveInteger');
570 $form->add('number', 'age_high', ts('Max Age'), ['class' => 'four', 'min' => 0]);
571 $form->addRule('age_high', ts('Please enter a positive integer'), 'positiveInteger');
572 $form->add('datepicker', 'age_asof_date', ts('As of'), NULL, FALSE, ['time' => FALSE]);
578 public static function notes(&$form) {
579 $form->add('hidden', 'hidden_notes', 1);
582 2 => ts('Body Only'),
583 3 => ts('Subject Only'),
586 $form->addRadio('note_option', '', $options);
588 $form->addElement('text', 'note', ts('Note Text'), CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
590 $form->setDefaults(['note_option' => 6]);
594 * Generate the custom Data Fields based for those with is_searchable = 1.
596 * @param CRM_Contact_Form_Search $form
598 * @throws \CiviCRM_API3_Exception
600 public static function custom(&$form) {
601 $form->add('hidden', 'hidden_custom', 1);
602 $extends = array_merge(['Contact', 'Individual', 'Household', 'Organization'],
603 CRM_Contact_BAO_ContactType
::subTypes()
605 $groupDetails = CRM_Core_BAO_CustomGroup
::getGroupDetail(NULL, TRUE,
609 $form->assign('groupTree', $groupDetails);
611 foreach ($groupDetails as $key => $group) {
612 $_groupTitle[$key] = $group['name'];
614 foreach ($group['fields'] as $field) {
615 $fieldId = $field['id'];
616 $elementName = 'custom_' . $fieldId;
617 if ($field['data_type'] === 'Date' && $field['is_search_range']) {
618 $form->addDatePickerRange($elementName, $field['label']);
621 CRM_Core_BAO_CustomField
::addQuickFormElement($form, $elementName, $fieldId, FALSE, TRUE);
630 public static function CiviCase(&$form) {
631 //Looks like obsolete code, since CiviCase is a component, but might be used by HRD
632 $form->add('hidden', 'hidden_CiviCase', 1);
633 CRM_Case_BAO_Query
::buildSearchForm($form);