Merge pull request #18631 from eileenmcnaughton/ppp
[civicrm-core.git] / CRM / Contact / Form / Search / Advanced.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
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 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
5a409b50 19 * Advanced search, extends basic search.
6a488035
TO
20 */
21class CRM_Contact_Form_Search_Advanced extends CRM_Contact_Form_Search {
22
23 /**
fe482240 24 * Processing needed for buildForm and later.
6a488035 25 */
00be9182 26 public function preProcess() {
2d09a0c3 27 // SearchFormName is deprecated & to be removed - the replacement is for the task to
28 // call $this->form->getSearchFormValues()
29 // A couple of extensions use it.
6a488035
TO
30 $this->set('searchFormName', 'Advanced');
31
32 parent::preProcess();
33 $openedPanes = CRM_Contact_BAO_Query::$_openedPanes;
34 $openedPanes = array_merge($openedPanes, $this->_openedPanes);
35 $this->assign('openedPanes', $openedPanes);
36 }
37
38 /**
fe482240 39 * Build the form object.
6a488035 40 */
00be9182 41 public function buildQuickForm() {
6a488035
TO
42 $this->set('context', 'advanced');
43
9c1bc317 44 $this->_searchPane = $_GET['searchPane'] ?? NULL;
6a488035
TO
45
46 $this->_searchOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
47 'advanced_search_options'
48 );
49
50 if (!$this->_searchPane || $this->_searchPane == 'basic') {
51 CRM_Contact_Form_Search_Criteria::basic($this);
52 }
53
2c4998cc 54 $allPanes = [];
55 $paneNames = [
6a488035
TO
56 ts('Address Fields') => 'location',
57 ts('Custom Fields') => 'custom',
58 ts('Activities') => 'activity',
59 ts('Relationships') => 'relationship',
60 ts('Demographics') => 'demographics',
61 ts('Notes') => 'notes',
62 ts('Change Log') => 'changeLog',
2c4998cc 63 ];
6a488035
TO
64
65 //check if there are any custom data searchable fields
2c4998cc 66 $extends = array_merge(['Contact', 'Individual', 'Household', 'Organization'],
6a488035
TO
67 CRM_Contact_BAO_ContactType::subTypes()
68 );
69 $groupDetails = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, TRUE,
70 $extends
71 );
72 // if no searchable fields unset panel
73 if (empty($groupDetails)) {
74 unset($paneNames[ts('Custom Fields')]);
75 }
76
77 foreach ($paneNames as $name => $type) {
78 if (!$this->_searchOptions[$type]) {
79 unset($paneNames[$name]);
80 }
81 }
82
83 $components = CRM_Core_Component::getEnabledComponents();
84
2c4998cc 85 $componentPanes = [];
6a488035
TO
86 foreach ($components as $name => $component) {
87 if (in_array($name, array_keys($this->_searchOptions)) &&
88 $this->_searchOptions[$name] &&
89 CRM_Core_Permission::access($component->name)
90 ) {
91 $componentPanes[$name] = $component->registerAdvancedSearchPane();
92 $componentPanes[$name]['name'] = $name;
93 }
94 }
95
2c4998cc 96 usort($componentPanes, ['CRM_Utils_Sort', 'cmpFunc']);
6a488035
TO
97 foreach ($componentPanes as $name => $pane) {
98 // FIXME: we should change the use of $name here to keyword
99 $paneNames[$pane['title']] = $pane['name'];
100 }
101
2c4998cc 102 $hookPanes = [];
efa3a566
DS
103 CRM_Contact_BAO_Query_Hook::singleton()->registerAdvancedSearchPane($hookPanes);
104 $paneNames = array_merge($paneNames, $hookPanes);
6a488035 105
2c4998cc 106 $this->_paneTemplatePath = [];
6a488035 107 foreach ($paneNames as $name => $type) {
efa3a566 108 if (!array_key_exists($type, $this->_searchOptions) && !in_array($type, $hookPanes)) {
6a488035
TO
109 continue;
110 }
111
2c4998cc 112 $allPanes[$name] = [
6a488035
TO
113 'url' => CRM_Utils_System::url('civicrm/contact/search/advanced',
114 "snippet=1&searchPane=$type&qfKey={$this->controller->_key}"
115 ),
116 'open' => 'false',
117 'id' => $type,
2c4998cc 118 ];
6a488035
TO
119
120 // see if we need to include this paneName in the current form
8cc574cf 121 if ($this->_searchPane == $type || !empty($_POST["hidden_{$type}"]) ||
b99f3e96 122 !empty($this->_formValues["hidden_{$type}"])
6a488035
TO
123 ) {
124 $allPanes[$name]['open'] = 'true';
125
a7488080 126 if (!empty($components[$type])) {
6a488035
TO
127 $c = $components[$type];
128 $this->add('hidden', "hidden_$type", 1);
129 $c->buildAdvancedSearchPaneForm($this);
130 $this->_paneTemplatePath[$type] = $c->getAdvancedSearchPaneTemplatePath();
131 }
4c9b6178 132 elseif (in_array($type, $hookPanes)) {
efa3a566
DS
133 CRM_Contact_BAO_Query_Hook::singleton()->buildAdvancedSearchPaneForm($this, $type);
134 CRM_Contact_BAO_Query_Hook::singleton()->setAdvancedSearchPaneTemplatePath($this->_paneTemplatePath, $type);
135 }
6a488035 136 else {
0e6e8724 137 CRM_Contact_Form_Search_Criteria::$type($this);
6a488035
TO
138 $template = ucfirst($type);
139 $this->_paneTemplatePath[$type] = "CRM/Contact/Form/Search/Criteria/{$template}.tpl";
140 }
141 }
142 }
143
144 $this->assign('allPanes', $allPanes);
145 if (!$this->_searchPane) {
146 parent::buildQuickForm();
147 }
148 else {
149 $this->assign('suppressForm', TRUE);
150 }
151 }
152
86538308 153 /**
fe482240 154 * Use the form name to create the tpl file name.
86538308
EM
155 *
156 * @return string
86538308 157 */
69078420 158
86538308
EM
159 /**
160 * @return string
161 */
00be9182 162 public function getTemplateFileName() {
6a488035
TO
163 if (!$this->_searchPane) {
164 return parent::getTemplateFileName();
165 }
166 else {
167 if (isset($this->_paneTemplatePath[$this->_searchPane])) {
168 return $this->_paneTemplatePath[$this->_searchPane];
169 }
170 else {
171 $name = ucfirst($this->_searchPane);
172 return "CRM/Contact/Form/Search/Criteria/{$name}.tpl";
173 }
174 }
175 }
176
177 /**
fe482240 178 * Set the default form values.
6a488035 179 *
a6c01b45
CW
180 * @return array
181 * the default array reference
8c9caddc 182 * @throws \Exception
6a488035 183 */
00be9182 184 public function setDefaultValues() {
8c9caddc 185 $defaults = parent::setDefaultValues();
41e6c841 186 // Set ssID for unit tests.
187 if (empty($this->_ssID)) {
188 $this->_ssID = $this->get('ssID');
189 }
190
e9f51713 191 $defaults = array_merge((array) $this->_formValues, [
852cda66 192 'privacy_toggle' => 1,
4abd8340 193 'operator' => 'AND',
8c9caddc 194 ], $defaults);
b0152e81 195 $defaults = $this->normalizeDefaultValues($defaults);
6a488035 196
75f21289 197 //991/Subtypes not respected when editing smart group criteria
d968e78c 198 if (!empty($defaults['contact_type']) && !empty($this->_formValues['contact_sub_type'])) {
75f21289
AW
199 foreach ($this->_formValues['contact_sub_type'] as $subtype) {
200 $basicType = CRM_Contact_BAO_ContactType::getBasicType($subtype);
201 $defaults['contact_type'][$subtype] = $basicType . '__' . $subtype;
202 }
203 }
2cd66e59 204
6a488035 205 if ($this->_context === 'amtg') {
eda34f9b 206 $defaults['task'] = CRM_Contact_Task::GROUP_ADD;
6a488035 207 }
6a488035
TO
208 return $defaults;
209 }
210
211 /**
212 * The post processing of the form gets done here.
213 *
214 * Key things done during post processing are
5a409b50 215 * - check for reset or next request. if present, skip post processing.
6a488035
TO
216 * - now check if user requested running a saved search, if so, then
217 * the form values associated with the saved search are used for searching.
5a409b50 218 * - if user has done a submit with new values the regular post submitting is
6a488035
TO
219 * done.
220 * The processing consists of using a Selector / Controller framework for getting the
221 * search results.
6a488035 222 */
00be9182 223 public function postProcess() {
6a488035
TO
224 $this->set('isAdvanced', '1');
225
359fdb6f 226 $this->setFormValues();
6a488035
TO
227 // get user submitted values
228 // get it from controller only if form has been submitted, else preProcess has set this
229 if (!empty($_POST)) {
6a488035
TO
230 $this->normalizeFormValues();
231 // FIXME: couldn't figure out a good place to do this,
232 // FIXME: so leaving this as a dependency for now
233 if (array_key_exists('contribution_amount_low', $this->_formValues)) {
2c4998cc 234 foreach (['contribution_amount_low', 'contribution_amount_high'] as $f) {
6a488035
TO
235 $this->_formValues[$f] = CRM_Utils_Rule::cleanMoney($this->_formValues[$f]);
236 }
237 }
238
239 // set the group if group is submitted
81aa6678 240 if (!empty($this->_formValues['uf_group_id'])) {
6a488035
TO
241 $this->set('id', $this->_formValues['uf_group_id']);
242 }
243 else {
244 $this->set('id', '');
245 }
246 }
247
248 // retrieve ssID values only if formValues is null, i.e. form has never been posted
249 if (empty($this->_formValues) && isset($this->_ssID)) {
250 $this->_formValues = CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID);
251 }
252
8cc574cf 253 if (isset($this->_groupID) && empty($this->_formValues['group'])) {
2c4998cc 254 $this->_formValues['group'] = [$this->_groupID => 1];
6a488035
TO
255 }
256
6a488035
TO
257 //search for civicase
258 if (is_array($this->_formValues)) {
259 $allCases = FALSE;
260 if (array_key_exists('case_owner', $this->_formValues) &&
261 !$this->_formValues['case_owner'] &&
262 !$this->_force
263 ) {
2c4998cc 264 foreach (['case_type_id', 'case_status_id', 'case_deleted', 'case_tags'] as $caseCriteria) {
a7488080 265 if (!empty($this->_formValues[$caseCriteria])) {
6a488035
TO
266 $allCases = TRUE;
267 $this->_formValues['case_owner'] = 1;
268 continue;
269 }
270 }
271 if ($allCases) {
272 if (CRM_Core_Permission::check('access all cases and activities')) {
273 $this->_formValues['case_owner'] = 1;
274 }
275 else {
276 $this->_formValues['case_owner'] = 2;
277 }
278 }
279 else {
280 $this->_formValues['case_owner'] = 0;
281 }
282 }
d8f7f92c
RK
283 if (array_key_exists('case_owner', $this->_formValues) && empty($this->_formValues['case_deleted'])) {
284 $this->_formValues['case_deleted'] = 0;
285 }
6a488035
TO
286 }
287
288 // we dont want to store the sortByCharacter in the formValue, it is more like
289 // a filter on the result set
290 // this filter is reset if we click on the search button
e166ff79 291 if ($this->_sortByCharacter !== NULL && empty($_POST)) {
6a488035
TO
292 if (strtolower($this->_sortByCharacter) == 'all') {
293 $this->_formValues['sortByCharacter'] = NULL;
294 }
295 else {
296 $this->_formValues['sortByCharacter'] = $this->_sortByCharacter;
297 }
298 }
e166ff79
CW
299 else {
300 $this->_sortByCharacter = NULL;
301 }
6a488035 302
87061940 303 $this->_params = CRM_Contact_BAO_Query::convertFormValues($this->_formValues, 0, FALSE, NULL, $this->entityReferenceFields);
6a488035
TO
304 $this->_returnProperties = &$this->returnProperties();
305 parent::postProcess();
306 }
307
308 /**
5a409b50 309 * Normalize the form values to make it look similar to the advanced form values.
6a488035 310 *
5a409b50 311 * This prevents a ton of work downstream and allows us to use the same code for
312 * multiple purposes (queries, save/edit etc)
6a488035 313 */
00be9182 314 public function normalizeFormValues() {
9c1bc317 315 $contactType = $this->_formValues['contact_type'] ?? NULL;
6a488035
TO
316
317 if ($contactType && is_array($contactType)) {
318 unset($this->_formValues['contact_type']);
319 foreach ($contactType as $key => $value) {
320 $this->_formValues['contact_type'][$value] = 1;
321 }
322 }
323
324 $config = CRM_Core_Config::singleton();
2c4998cc 325 $specialParams = [
afa0b07c 326 'financial_type_id',
327 'contribution_soft_credit_type_id',
328 'contribution_status',
fb11d707 329 'contribution_status_id',
994bde4e
SB
330 'membership_status_id',
331 'participant_status_id',
afa0b07c 332 'contribution_trxn_id',
333 'activity_type_id',
da236f9a 334 'priority_id',
6ffab5b7 335 'contribution_product_id',
8f3dc989 336 'payment_instrument_id',
7cc09daf 337 'group',
338 'contact_tags',
339 'preferred_communication_method',
2c4998cc 340 ];
341 $changeNames = [
da236f9a 342 'priority_id' => 'activity_priority_id',
2c4998cc 343 ];
0b38e8f1 344 CRM_Contact_BAO_Query::processSpecialFormValue($this->_formValues, $specialParams, $changeNames);
86538308 345
9c1bc317 346 $taglist = $this->_formValues['contact_taglist'] ?? NULL;
6a488035
TO
347
348 if ($taglist && is_array($taglist)) {
349 unset($this->_formValues['contact_taglist']);
350 foreach ($taglist as $value) {
351 if ($value) {
352 $value = explode(',', $value);
fe9517de 353 foreach ($value as $tId) {
354 if (is_numeric($tId)) {
355 $this->_formValues['contact_tags'][] = $tId;
356 }
357 }
6a488035
TO
358 }
359 }
360 }
6a488035
TO
361 }
362
363 /**
fe482240 364 * Normalize default values for multiselect plugins.
6a488035 365 *
5a4f6742 366 * @param array $defaults
bb05da0c 367 *
f4bff68a 368 * @return array
6a488035 369 */
d81916a2 370 public function normalizeDefaultValues($defaults) {
20306bb8 371 $this->loadDefaultCountryBasedOnState($defaults);
6a488035 372 if ($this->_ssID && empty($_POST)) {
06d67d53 373 $defaults = array_merge($defaults, CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID));
6a488035 374 }
1a4127a0 375
376 /*
377 * CRM-18656 - reverse the normalisation of 'contact_taglist' done in
378 * self::normalizeFormValues(). Remove tagset tags from the default
379 * 'contact_tags' and put them in 'contact_taglist[N]' where N is the
380 * id of the tagset.
381 */
382 if (isset($defaults['contact_tags'])) {
d7cc9ca9 383 foreach ((array) $defaults['contact_tags'] as $key => $tagId) {
08de6823 384 if (!is_array($tagId)) {
385 $parentId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Tag', $tagId, 'parent_id');
386 $element = "contact_taglist[$parentId]";
387 if ($this->elementExists($element)) {
388 // This tag is a tagset
389 unset($defaults['contact_tags'][$key]);
390 if (!isset($defaults[$element])) {
2c4998cc 391 $defaults[$element] = [];
08de6823 392 }
393 $defaults[$element][] = $tagId;
1a4127a0 394 }
1a4127a0 395 }
396 }
397 if (empty($defaults['contact_tags'])) {
398 unset($defaults['contact_tags']);
399 }
400 }
401
6a488035
TO
402 return $defaults;
403 }
96025800 404
20306bb8 405 /**
406 * Set the default country for the form.
407 *
408 * For performance reasons country might be removed from the form CRM-18125
409 * but we need to include it in our defaults or the state will not be visible.
410 *
411 * @param array $defaults
412 */
413 public function loadDefaultCountryBasedOnState(&$defaults) {
414 if (!empty($defaults['state_province'])) {
415 $defaults['country'] = CRM_Core_DAO::singleValueQuery(
416 "SELECT country_id FROM civicrm_state_province
417 WHERE id = %1",
2c4998cc 418 [1 => [$defaults['state_province'][0], 'Integer']]
20306bb8 419 );
420 }
421 }
422
6a488035 423}