Merge pull request #22264 from sunilpawar/dev_recurring_error
[civicrm-core.git] / CRM / Contact / BAO / SavedSearch.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/**
c037736a 19 * Business object for Saved searches.
6a488035
TO
20 */
21class CRM_Contact_BAO_SavedSearch extends CRM_Contact_DAO_SavedSearch {
22
6a488035 23 /**
100fef9d 24 * Query the db for all saved searches.
6a488035 25 *
a6c01b45
CW
26 * @return array
27 * contains the search name as value and and id as key
6a488035 28 */
00be9182 29 public function getAll() {
6a488035
TO
30 $savedSearch = new CRM_Contact_DAO_SavedSearch();
31 $savedSearch->selectAdd();
32 $savedSearch->selectAdd('id, name');
33 $savedSearch->find();
34 while ($savedSearch->fetch()) {
35 $aSavedSearch[$savedSearch->id] = $savedSearch->name;
36 }
37 return $aSavedSearch;
38 }
39
40 /**
fe482240
EM
41 * Retrieve DB object based on input parameters.
42 *
43 * It also stores all the retrieved values in the default array.
6a488035 44 *
77c5b619
TO
45 * @param array $params
46 * (reference ) an assoc array of name/value pairs.
47 * @param array $defaults
48 * (reference ) an assoc array to hold the flattened values.
6a488035 49 *
4cb27797 50 * @return CRM_Contact_DAO_SavedSearch
6a488035 51 */
4cb27797 52 public static function retrieve($params, &$defaults = []) {
6a488035
TO
53 $savedSearch = new CRM_Contact_DAO_SavedSearch();
54 $savedSearch->copyValues($params);
55 if ($savedSearch->find(TRUE)) {
56 CRM_Core_DAO::storeValues($savedSearch, $defaults);
57 return $savedSearch;
58 }
59 return NULL;
60 }
61
62 /**
54957108 63 * Given an id, extract the formValues of the saved search.
6a488035 64 *
77c5b619
TO
65 * @param int $id
66 * The id of the saved search.
6a488035 67 *
a6c01b45 68 * @return array
9406ae0b 69 * the values of the posted saved search used as default values in various Search Form
5bb33398 70 *
71 * @throws \CRM_Core_Exception
72 * @throws \CiviCRM_API3_Exception
6a488035 73 */
e5ad0335 74 public static function getFormValues($id) {
be2fb01f 75 $specialDateFields = [
8f271253
JP
76 'event_start_date_low' => 'event_date_low',
77 'event_end_date_high' => 'event_date_high',
be2fb01f 78 ];
8f271253 79
6a488035 80 $fv = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $id, 'form_values');
5bb33398 81 $result = [];
6a488035 82 if ($fv) {
f24846d5
SL
83 // make sure u CRM_Utils_String::unserialize - since it's stored in serialized form
84 $result = CRM_Utils_String::unserialize($fv);
6a488035
TO
85 }
86
be2fb01f 87 $specialFields = ['contact_type', 'group', 'contact_tags', 'member_membership_type_id', 'member_status_id'];
06d67d53 88 foreach ($result as $element => $value) {
89 if (CRM_Contact_BAO_Query::isAlreadyProcessedForQueryFormat($value)) {
9c1bc317
CW
90 $id = $value[0] ?? NULL;
91 $value = $value[2] ?? NULL;
06d67d53 92 if (is_array($value) && in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
8e2e771b 93 $op = key($value);
9c1bc317 94 $value = $value[$op] ?? NULL;
be2fb01f 95 if (in_array($op, ['BETWEEN', '>=', '<='])) {
8e2e771b 96 self::decodeRelativeFields($result, $id, $op, $value);
97 unset($result[$element]);
98 continue;
99 }
06d67d53 100 }
55ea4c30
JM
101 // Check for a date range field, which might be a standard date
102 // range or a relative date.
709355aa 103 if (strpos($id, '_date_low') !== FALSE || strpos($id, '_date_high') !== FALSE) {
5aa7bc38 104 $entityName = strstr($id, '_date', TRUE);
55ea4c30
JM
105
106 // This is the default, for non relative dates. We will overwrite
107 // it if we determine this is a relative date.
108 $result[$id] = $value;
109 $result["{$entityName}_date_relative"] = 0;
110
111 if (!empty($result['relative_dates'])) {
112 if (array_key_exists($entityName, $result['relative_dates'])) {
113 // We have a match from a regular field.
114 $result[$id] = NULL;
115 $result["{$entityName}_date_relative"] = $result['relative_dates'][$entityName];
116 }
117 elseif (!empty($specialDateFields[$id])) {
118 // We may have a match on a special date field.
119 $entityName = strstr($specialDateFields[$id], '_date', TRUE);
120 if (array_key_exists($entityName, $result['relative_dates'])) {
121 $result[$id] = NULL;
122 $result["{$entityName}_relative"] = $result['relative_dates'][$entityName];
123 }
124 }
9451fffa 125 }
709355aa 126 }
127 else {
128 $result[$id] = $value;
129 }
06d67d53 130 unset($result[$element]);
131 continue;
132 }
133 if (!empty($value) && is_array($value)) {
134 if (in_array($element, $specialFields)) {
e5ad0335
E
135 // Remove the element to minimise support for legacy formats. It is stored in $value
136 // so will be re-set with the right name.
137 unset($result[$element]);
06d67d53 138 $element = str_replace('member_membership_type_id', 'membership_type_id', $element);
139 $element = str_replace('member_status_id', 'membership_status_id', $element);
140 CRM_Contact_BAO_Query::legacyConvertFormValues($element, $value);
141 $result[$element] = $value;
142 }
143 // As per the OK (Operator as Key) value format, value array may contain key
144 // as an operator so to ensure the default is always set actual value
145 elseif (in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
9c1bc317 146 $result[$element] = $value[key($value)] ?? NULL;
06d67d53 147 if (is_string($result[$element])) {
148 $result[$element] = str_replace("%", '', $result[$element]);
6a488035 149 }
6a488035 150 }
06d67d53 151 }
c0b26fcd
SL
152 // We should only set the relative key for custom date fields if it is not already set in the array.
153 $realField = str_replace(['_relative', '_low', '_high', '_to', '_high'], '', $element);
154 if (substr($element, 0, 7) == 'custom_' && CRM_Contact_BAO_Query::isCustomDateField($realField)) {
155 if (!isset($result[$realField . '_relative'])) {
156 $result[$realField . '_relative'] = 0;
06d67d53 157 }
158 }
159 // check to see if we need to convert the old privacy array
160 // CRM-9180
161 if (!empty($result['privacy'])) {
162 if (is_array($result['privacy'])) {
163 $result['privacy_operator'] = 'AND';
164 $result['privacy_toggle'] = 1;
165 if (isset($result['privacy']['do_not_toggle'])) {
166 if ($result['privacy']['do_not_toggle']) {
167 $result['privacy_toggle'] = 2;
168 }
169 unset($result['privacy']['do_not_toggle']);
170 }
6a488035 171
be2fb01f 172 $result['privacy_options'] = [];
d0d565e0 173 foreach ($result['privacy'] as $name => $val) {
174 if ($val) {
06d67d53 175 $result['privacy_options'][] = $name;
176 }
6a488035
TO
177 }
178 }
06d67d53 179 unset($result['privacy']);
6a488035 180 }
6a488035
TO
181 }
182
3c49839d 183 if ($customSearchClass = CRM_Utils_Array::value('customSearchClass', $result)) {
184 // check if there is a special function - formatSavedSearchFields defined in the custom search form
185 if (method_exists($customSearchClass, 'formatSavedSearchFields')) {
186 $customSearchClass::formatSavedSearchFields($result);
187 }
188 }
189
6a488035
TO
190 return $result;
191 }
192
86538308 193 /**
54957108 194 * Get search parameters.
195 *
100fef9d 196 * @param int $id
86538308
EM
197 *
198 * @return array
5bb33398 199 *
200 * @throws \CRM_Core_Exception
201 * @throws \CiviCRM_API3_Exception
86538308 202 */
00be9182 203 public static function getSearchParams($id) {
fe806431 204 $savedSearch = \Civi\Api4\SavedSearch::get(FALSE)
4e97c268
CW
205 ->addWhere('id', '=', $id)
206 ->execute()
207 ->first();
208 if ($savedSearch['api_entity']) {
209 return $savedSearch;
210 }
6a488035 211 $fv = self::getFormValues($id);
959528d2 212 //check if the saved search has mapping id
4e97c268 213 if ($savedSearch['mapping_id']) {
6a488035
TO
214 return CRM_Core_BAO_Mapping::formattedFields($fv);
215 }
a7488080 216 elseif (!empty($fv['customSearchID'])) {
6a488035
TO
217 return $fv;
218 }
219 else {
220 return CRM_Contact_BAO_Query::convertFormValues($fv);
221 }
222 }
223
224 /**
fe482240 225 * Get the where clause for a saved search.
6a488035 226 *
77c5b619
TO
227 * @param int $id
228 * Saved search id.
229 * @param array $tables
230 * (reference ) add the tables that are needed for the select clause.
231 * @param array $whereTables
232 * (reference ) add the tables that are needed for the where clause.
6a488035 233 *
a6c01b45
CW
234 * @return string
235 * the where clause for this saved search
6a488035 236 */
00be9182 237 public static function whereClause($id, &$tables, &$whereTables) {
6a488035
TO
238 $params = self::getSearchParams($id);
239 if ($params) {
a7488080 240 if (!empty($params['customSearchID'])) {
6a488035 241 // this has not yet been implemented
0db6c3e1
TO
242 }
243 else {
353ffa53
TO
244 return CRM_Contact_BAO_Query::getWhereClause($params, NULL, $tables, $whereTables);
245 }
6a488035
TO
246 }
247 return NULL;
248 }
249
86538308 250 /**
54957108 251 * Contact IDS Sql (whatever that means!).
252 *
100fef9d 253 * @param int $id
86538308
EM
254 *
255 * @return string
256 */
00be9182 257 public static function contactIDsSQL($id) {
6a488035 258 $params = self::getSearchParams($id);
8cc574cf 259 if ($params && !empty($params['customSearchID'])) {
6a488035
TO
260 return CRM_Contact_BAO_SearchCustom::contactIDSQL(NULL, $id);
261 }
262 else {
be2fb01f 263 $tables = $whereTables = ['civicrm_contact' => 1];
6a488035
TO
264 $where = CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables);
265 if (!$where) {
266 $where = '( 1 )';
267 }
268 $from = CRM_Contact_BAO_Query::fromClause($whereTables);
269 return "
270SELECT contact_a.id
271$from
272WHERE $where";
273 }
274 }
275
86538308 276 /**
54957108 277 * Get from where email (whatever that means!).
278 *
100fef9d 279 * @param int $id
86538308
EM
280 *
281 * @return array
282 */
00be9182 283 public static function fromWhereEmail($id) {
6a488035
TO
284 $params = self::getSearchParams($id);
285
286 if ($params) {
a7488080 287 if (!empty($params['customSearchID'])) {
6a488035
TO
288 return CRM_Contact_BAO_SearchCustom::fromWhereEmail(NULL, $id);
289 }
290 else {
be2fb01f 291 $tables = $whereTables = ['civicrm_contact' => 1, 'civicrm_email' => 1];
353ffa53
TO
292 $where = CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables);
293 $from = CRM_Contact_BAO_Query::fromClause($whereTables);
be2fb01f 294 return [$from, $where];
6a488035
TO
295 }
296 }
297 else {
298 // fix for CRM-7240
299 $from = "
300FROM civicrm_contact contact_a
301LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_email.is_primary = 1)
302";
303 $where = " ( 1 ) ";
304 $tables['civicrm_contact'] = $whereTables['civicrm_contact'] = 1;
305 $tables['civicrm_email'] = $whereTables['civicrm_email'] = 1;
be2fb01f 306 return [$from, $where];
6a488035
TO
307 }
308 }
309
6a488035 310 /**
e8e8f3ad 311 * Given an id, get the name of the saved search.
6a488035 312 *
77c5b619
TO
313 * @param int $id
314 * The id of the saved search.
6a488035 315 *
77b97be7
EM
316 * @param string $value
317 *
a6c01b45
CW
318 * @return string
319 * the name of the saved search
6a488035 320 */
00be9182 321 public static function getName($id, $value = 'name') {
6a488035
TO
322 $group = new CRM_Contact_DAO_Group();
323 $group->saved_search_id = $id;
324 if ($group->find(TRUE)) {
325 return $group->$value;
326 }
327 return NULL;
328 }
329
330 /**
53b13ffe 331 * Create or update SavedSearch record.
54957108 332 *
333 * @param array $params
334 *
335 * @return \CRM_Contact_DAO_SavedSearch
6a488035 336 */
00be9182 337 public static function create(&$params) {
53b13ffe
CW
338 // Auto-create unique name from label if supplied
339 if (empty($params['id']) && empty($params['name']) && !empty($params['label'])) {
340 $name = CRM_Utils_String::munge($params['label']);
341 $existing = Civi\Api4\SavedSearch::get(FALSE)
342 ->addWhere('name', 'LIKE', $name . '%')
343 ->addSelect('name')
344 ->execute()->column('name');
345 $suffix = '';
346 while (in_array($name . $suffix, $existing)) {
347 $suffix = '_' . (1 + str_replace('_', '', $suffix));
348 }
349 $params['name'] = $name . $suffix;
350 }
49a1050c 351 $loggedInContactID = CRM_Core_Session::getLoggedInContactID();
352 if ($loggedInContactID) {
353 if (empty($params['id'])) {
354 $params['created_id'] = $loggedInContactID;
355 }
356 $params['modified_id'] = $loggedInContactID;
357 }
1e0a0433
CW
358 // Set by mysql
359 unset($params['modified_date']);
7ec684ff
CW
360
361 // Flush angular caches to refresh search displays
362 if (isset($params['api_params'])) {
363 Civi::container()->get('angular')->clear();
364 }
53b13ffe 365 return self::writeRecord($params);
6a488035 366 }
96025800 367
54957108 368 /**
369 * Assign test value.
370 *
371 * @param string $fieldName
372 * @param array $fieldDef
373 * @param int $counter
374 */
4f0a0e58
JV
375 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
376 if ($fieldName == 'form_values') {
377 // A dummy value for form_values.
378 $this->{$fieldName} = serialize(
be2fb01f 379 ['sort_name' => "SortName{$counter}"]);
4f0a0e58
JV
380 }
381 else {
382 parent::assignTestValues($fieldName, $fieldDef, $counter);
383 }
384 }
385
3f471f2d 386 /**
387 * Store search variables in $queryParams which were skipped while processing query params,
388 * precisely at CRM_Contact_BAO_Query::fixWhereValues(...). But these variable are required in
389 * building smart group criteria otherwise it will cause issues like CRM-18585,CRM-19571
390 *
391 * @param array $queryParams
392 * @param array $formValues
393 */
394 public static function saveSkippedElement(&$queryParams, $formValues) {
395 // these are elements which are skipped in a smart group criteria
be2fb01f 396 $specialElements = [
3f471f2d 397 'operator',
398 'component_mode',
399 'display_relationship_type',
ce023e5d 400 'uf_group_id',
be2fb01f 401 ];
3f471f2d 402 foreach ($specialElements as $element) {
403 if (!empty($formValues[$element])) {
be2fb01f 404 $queryParams[] = [$element, '=', $formValues[$element], 0, 0];
3f471f2d 405 }
406 }
407 }
408
8e2e771b 409 /**
410 * Decode relative custom fields (converted by CRM_Contact_BAO_Query->convertCustomRelativeFields(...))
411 * into desired formValues
412 *
413 * @param array $formValues
414 * @param string $fieldName
415 * @param string $op
416 * @param array|string|int $value
afbe25c1 417 *
418 * @throws \CiviCRM_API3_Exception
8e2e771b 419 */
420 public static function decodeRelativeFields(&$formValues, $fieldName, $op, $value) {
b2b9d9df 421 // check if its a custom date field, if yes then 'searchDate' format the value
afbe25c1 422 if (CRM_Contact_BAO_Query::isCustomDateField($fieldName)) {
423 return;
d8766e36 424 }
afbe25c1 425
8e2e771b 426 switch ($op) {
427 case 'BETWEEN':
49a1050c 428 [$formValues[$fieldName . '_from'], $formValues[$fieldName . '_to']] = $value;
8e2e771b 429 break;
430
431 case '>=':
afbe25c1 432 $formValues[$fieldName . '_from'] = $value;
8e2e771b 433 break;
434
435 case '<=':
afbe25c1 436 $formValues[$fieldName . '_to'] = $value;
8e2e771b 437 break;
438 }
439 }
440
4cb27797
CW
441 /**
442 * Generate a url to the appropriate search form for a given savedSearch
443 *
444 * @param int $id
445 * Saved search id
446 * @return string
447 */
448 public static function getEditSearchUrl($id) {
449 $savedSearch = self::retrieve(['id' => $id]);
450 // APIv4 search
451 if (!empty($savedSearch->api_entity)) {
44402a2e 452 return CRM_Utils_System::url('civicrm/admin/search', NULL, FALSE, "/edit/$id");
4cb27797
CW
453 }
454 // Classic search builder
455 if (!empty($savedSearch->mapping_id)) {
456 $path = 'civicrm/contact/search/builder';
457 }
458 // Classic custom search
459 elseif (!empty($savedSearch->search_custom_id)) {
460 $path = 'civicrm/contact/search/custom';
461 }
462 // Classic advanced search
463 else {
464 $path = 'civicrm/contact/search/advanced';
465 }
466 return CRM_Utils_System::url($path, ['reset' => 1, 'ssID' => $id]);
467 }
468
ddc4b6b8
CW
469 /**
470 * Retrieve pseudoconstant options for $this->api_entity field
471 * @return array
472 */
473 public static function getApiEntityOptions() {
474 return Civi\Api4\Entity::get(FALSE)
475 ->addSelect('name', 'title_plural')
476 ->addOrderBy('title_plural')
477 ->execute()
478 ->indexBy('name')
479 ->column('title_plural');
480 }
481
6a488035 482}