Greeting handling - if email_greeting_custom (etc) isset & email_greeting_id is not...
[civicrm-core.git] / CRM / Contact / BAO / SavedSearch.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Business object for Saved searches.
20 */
21 class CRM_Contact_BAO_SavedSearch extends CRM_Contact_DAO_SavedSearch {
22
23 /**
24 * Retrieve DB object and copy to defaults array.
25 *
26 * @param array $params
27 * Array of criteria values.
28 * @param array $defaults
29 * Array to be populated with found values.
30 *
31 * @return self|null
32 * The DAO object, if found.
33 *
34 * @deprecated
35 */
36 public static function retrieve($params, &$defaults = []) {
37 return self::commonRetrieve(self::class, $params, $defaults);
38 }
39
40 /**
41 * Given an id, extract the formValues of the saved search.
42 *
43 * @param int $id
44 * The id of the saved search.
45 *
46 * @return array
47 * the values of the posted saved search used as default values in various Search Form
48 *
49 * @throws \CRM_Core_Exception
50 * @throws \CiviCRM_API3_Exception
51 */
52 public static function getFormValues($id) {
53 $specialDateFields = [
54 'event_start_date_low' => 'event_date_low',
55 'event_end_date_high' => 'event_date_high',
56 ];
57
58 $fv = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $id, 'form_values');
59 $result = [];
60 if ($fv) {
61 // make sure u CRM_Utils_String::unserialize - since it's stored in serialized form
62 $result = CRM_Utils_String::unserialize($fv);
63 }
64
65 $specialFields = ['contact_type', 'group', 'contact_tags', 'member_membership_type_id', 'member_status_id'];
66 foreach ($result as $element => $value) {
67 if (CRM_Contact_BAO_Query::isAlreadyProcessedForQueryFormat($value)) {
68 $id = $value[0] ?? NULL;
69 $value = $value[2] ?? NULL;
70 if (is_array($value) && in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
71 $op = key($value);
72 $value = $value[$op] ?? NULL;
73 if (in_array($op, ['BETWEEN', '>=', '<='])) {
74 self::decodeRelativeFields($result, $id, $op, $value);
75 unset($result[$element]);
76 continue;
77 }
78 }
79 // Check for a date range field, which might be a standard date
80 // range or a relative date.
81 if (strpos($id, '_date_low') !== FALSE || strpos($id, '_date_high') !== FALSE) {
82 $entityName = strstr($id, '_date', TRUE);
83
84 // This is the default, for non relative dates. We will overwrite
85 // it if we determine this is a relative date.
86 $result[$id] = $value;
87 $result["{$entityName}_date_relative"] = 0;
88
89 if (!empty($result['relative_dates'])) {
90 if (array_key_exists($entityName, $result['relative_dates'])) {
91 // We have a match from a regular field.
92 $result[$id] = NULL;
93 $result["{$entityName}_date_relative"] = $result['relative_dates'][$entityName];
94 }
95 elseif (!empty($specialDateFields[$id])) {
96 // We may have a match on a special date field.
97 $entityName = strstr($specialDateFields[$id], '_date', TRUE);
98 if (array_key_exists($entityName, $result['relative_dates'])) {
99 $result[$id] = NULL;
100 $result["{$entityName}_relative"] = $result['relative_dates'][$entityName];
101 }
102 }
103 }
104 }
105 else {
106 $result[$id] = $value;
107 }
108 unset($result[$element]);
109 continue;
110 }
111 if (!empty($value) && is_array($value)) {
112 if (in_array($element, $specialFields)) {
113 // Remove the element to minimise support for legacy formats. It is stored in $value
114 // so will be re-set with the right name.
115 unset($result[$element]);
116 $element = str_replace('member_membership_type_id', 'membership_type_id', $element);
117 $element = str_replace('member_status_id', 'membership_status_id', $element);
118 CRM_Contact_BAO_Query::legacyConvertFormValues($element, $value);
119 $result[$element] = $value;
120 }
121 // As per the OK (Operator as Key) value format, value array may contain key
122 // as an operator so to ensure the default is always set actual value
123 elseif (in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
124 $result[$element] = $value[key($value)] ?? NULL;
125 if (is_string($result[$element])) {
126 $result[$element] = str_replace("%", '', $result[$element]);
127 }
128 }
129 }
130 // We should only set the relative key for custom date fields if it is not already set in the array.
131 $realField = str_replace(['_relative', '_low', '_high', '_to', '_high'], '', $element);
132 if (substr($element, 0, 7) == 'custom_' && CRM_Contact_BAO_Query::isCustomDateField($realField)) {
133 if (!isset($result[$realField . '_relative'])) {
134 $result[$realField . '_relative'] = 0;
135 }
136 }
137 // check to see if we need to convert the old privacy array
138 // CRM-9180
139 if (!empty($result['privacy'])) {
140 if (is_array($result['privacy'])) {
141 $result['privacy_operator'] = 'AND';
142 $result['privacy_toggle'] = 1;
143 if (isset($result['privacy']['do_not_toggle'])) {
144 if ($result['privacy']['do_not_toggle']) {
145 $result['privacy_toggle'] = 2;
146 }
147 unset($result['privacy']['do_not_toggle']);
148 }
149
150 $result['privacy_options'] = [];
151 foreach ($result['privacy'] as $name => $val) {
152 if ($val) {
153 $result['privacy_options'][] = $name;
154 }
155 }
156 }
157 unset($result['privacy']);
158 }
159 }
160
161 if ($customSearchClass = CRM_Utils_Array::value('customSearchClass', $result)) {
162 // check if there is a special function - formatSavedSearchFields defined in the custom search form
163 if (method_exists($customSearchClass, 'formatSavedSearchFields')) {
164 $customSearchClass::formatSavedSearchFields($result);
165 }
166 }
167
168 return $result;
169 }
170
171 /**
172 * Get search parameters.
173 *
174 * @param int $id
175 *
176 * @return array
177 *
178 * @throws \CRM_Core_Exception
179 * @throws \CiviCRM_API3_Exception
180 */
181 public static function getSearchParams($id) {
182 $savedSearch = \Civi\Api4\SavedSearch::get(FALSE)
183 ->addWhere('id', '=', $id)
184 ->execute()
185 ->first();
186 if ($savedSearch['api_entity']) {
187 return $savedSearch;
188 }
189 $fv = self::getFormValues($id);
190 //check if the saved search has mapping id
191 if ($savedSearch['mapping_id']) {
192 return CRM_Core_BAO_Mapping::formattedFields($fv);
193 }
194 elseif (!empty($fv['customSearchID'])) {
195 return $fv;
196 }
197 else {
198 return CRM_Contact_BAO_Query::convertFormValues($fv);
199 }
200 }
201
202 /**
203 * Get the where clause for a saved search.
204 *
205 * @param int $id
206 * Saved search id.
207 * @param array $tables
208 * (reference ) add the tables that are needed for the select clause.
209 * @param array $whereTables
210 * (reference ) add the tables that are needed for the where clause.
211 *
212 * @return string
213 * the where clause for this saved search
214 */
215 public static function whereClause($id, &$tables, &$whereTables) {
216 $params = self::getSearchParams($id);
217 if ($params) {
218 if (!empty($params['customSearchID'])) {
219 // this has not yet been implemented
220 }
221 else {
222 return CRM_Contact_BAO_Query::getWhereClause($params, NULL, $tables, $whereTables);
223 }
224 }
225 return NULL;
226 }
227
228 /**
229 * Contact IDS Sql (whatever that means!).
230 *
231 * @param int $id
232 *
233 * @return string
234 */
235 public static function contactIDsSQL($id) {
236 $params = self::getSearchParams($id);
237 if ($params && !empty($params['customSearchID'])) {
238 return CRM_Contact_BAO_SearchCustom::contactIDSQL(NULL, $id);
239 }
240 else {
241 $tables = $whereTables = ['civicrm_contact' => 1];
242 $where = CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables);
243 if (!$where) {
244 $where = '( 1 )';
245 }
246 $from = CRM_Contact_BAO_Query::fromClause($whereTables);
247 return "
248 SELECT contact_a.id
249 $from
250 WHERE $where";
251 }
252 }
253
254 /**
255 * Deprecated function, gets a value from Group entity
256 *
257 * @deprecated
258 * @param int $id
259 * @param string $value
260 *
261 * @return string|null
262 */
263 public static function getName($id, $value = 'name') {
264 return parent::getFieldValue('CRM_Contact_DAO_Group', $id, $value, 'saved_search_id');
265 }
266
267 /**
268 * Create or update SavedSearch record.
269 *
270 * @param array $params
271 *
272 * @return \CRM_Contact_DAO_SavedSearch
273 */
274 public static function create(&$params) {
275 $loggedInContactID = CRM_Core_Session::getLoggedInContactID();
276 if ($loggedInContactID) {
277 if (empty($params['id'])) {
278 $params['created_id'] = $loggedInContactID;
279 }
280 $params['modified_id'] = $loggedInContactID;
281 }
282 // Set by mysql
283 unset($params['modified_date']);
284
285 // Flush angular caches to refresh search displays
286 if (isset($params['api_params'])) {
287 Civi::container()->get('angular')->clear();
288 }
289 return self::writeRecord($params);
290 }
291
292 /**
293 * Assign test value.
294 *
295 * @param string $fieldName
296 * @param array $fieldDef
297 * @param int $counter
298 */
299 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
300 if ($fieldName == 'form_values') {
301 // A dummy value for form_values.
302 $this->{$fieldName} = serialize(
303 ['sort_name' => "SortName{$counter}"]);
304 }
305 else {
306 parent::assignTestValues($fieldName, $fieldDef, $counter);
307 }
308 }
309
310 /**
311 * Store search variables in $queryParams which were skipped while processing query params,
312 * precisely at CRM_Contact_BAO_Query::fixWhereValues(...). But these variable are required in
313 * building smart group criteria otherwise it will cause issues like CRM-18585,CRM-19571
314 *
315 * @param array $queryParams
316 * @param array $formValues
317 */
318 public static function saveSkippedElement(&$queryParams, $formValues) {
319 // these are elements which are skipped in a smart group criteria
320 $specialElements = [
321 'operator',
322 'component_mode',
323 'display_relationship_type',
324 'uf_group_id',
325 ];
326 foreach ($specialElements as $element) {
327 if (!empty($formValues[$element])) {
328 $queryParams[] = [$element, '=', $formValues[$element], 0, 0];
329 }
330 }
331 }
332
333 /**
334 * Decode relative custom fields (converted by CRM_Contact_BAO_Query->convertCustomRelativeFields(...))
335 * into desired formValues
336 *
337 * @param array $formValues
338 * @param string $fieldName
339 * @param string $op
340 * @param array|string|int $value
341 *
342 * @throws \CiviCRM_API3_Exception
343 */
344 public static function decodeRelativeFields(&$formValues, $fieldName, $op, $value) {
345 // check if its a custom date field, if yes then 'searchDate' format the value
346 if (CRM_Contact_BAO_Query::isCustomDateField($fieldName)) {
347 return;
348 }
349
350 switch ($op) {
351 case 'BETWEEN':
352 [$formValues[$fieldName . '_from'], $formValues[$fieldName . '_to']] = $value;
353 break;
354
355 case '>=':
356 $formValues[$fieldName . '_from'] = $value;
357 break;
358
359 case '<=':
360 $formValues[$fieldName . '_to'] = $value;
361 break;
362 }
363 }
364
365 /**
366 * Generate a url to the appropriate search form for a given savedSearch
367 *
368 * @param int $id
369 * Saved search id
370 * @return string
371 */
372 public static function getEditSearchUrl($id) {
373 $savedSearch = self::retrieve(['id' => $id]);
374 // APIv4 search
375 if (!empty($savedSearch->api_entity)) {
376 return CRM_Utils_System::url('civicrm/admin/search', NULL, FALSE, "/edit/$id");
377 }
378 // Classic search builder
379 if (!empty($savedSearch->mapping_id)) {
380 $path = 'civicrm/contact/search/builder';
381 }
382 // Classic custom search
383 elseif (!empty($savedSearch->search_custom_id)) {
384 $path = 'civicrm/contact/search/custom';
385 }
386 // Classic advanced search
387 else {
388 $path = 'civicrm/contact/search/advanced';
389 }
390 return CRM_Utils_System::url($path, ['reset' => 1, 'ssID' => $id]);
391 }
392
393 /**
394 * Retrieve pseudoconstant options for $this->api_entity field
395 * @return array
396 */
397 public static function getApiEntityOptions() {
398 return Civi\Api4\Entity::get(FALSE)
399 ->addSelect('name', 'title_plural')
400 ->addOrderBy('title_plural')
401 ->execute()
402 ->indexBy('name')
403 ->column('title_plural');
404 }
405
406 }