Commit | Line | Data |
---|---|---|
3efb5b86 CW |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
fee14197 | 4 | | CiviCRM version 5 | |
3efb5b86 | 5 | +--------------------------------------------------------------------+ |
6b83d5bd | 6 | | Copyright CiviCRM LLC (c) 2004-2019 | |
3efb5b86 CW |
7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
11 | | under the terms of the GNU Affero General Public License | | |
c73475ea | 12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | |
3efb5b86 CW |
13 | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more details. | | |
18 | | | | |
19 | | You should have received a copy of the GNU Affero General Public | | |
c73475ea WA |
20 | | License and the CiviCRM Licensing Exception along | |
21 | | with this program; if not, contact CiviCRM LLC | | |
3efb5b86 CW |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
d25dd0ee | 26 | */ |
3efb5b86 CW |
27 | |
28 | /** | |
29 | * Base class for most search forms | |
30 | */ | |
31 | class CRM_Core_Form_Search extends CRM_Core_Form { | |
32 | ||
48473171 CW |
33 | /** |
34 | * Are we forced to run a search | |
35 | * | |
36 | * @var int | |
48473171 CW |
37 | */ |
38 | protected $_force; | |
39 | ||
40 | /** | |
100fef9d | 41 | * Name of search button |
48473171 CW |
42 | * |
43 | * @var string | |
48473171 CW |
44 | */ |
45 | protected $_searchButtonName; | |
46 | ||
47 | /** | |
100fef9d | 48 | * Name of action button |
48473171 CW |
49 | * |
50 | * @var string | |
48473171 CW |
51 | */ |
52 | protected $_actionButtonName; | |
53 | ||
54 | /** | |
100fef9d | 55 | * Form values that we will be using |
48473171 CW |
56 | * |
57 | * @var array | |
48473171 CW |
58 | */ |
59 | public $_formValues; | |
60 | ||
61 | /** | |
100fef9d | 62 | * Have we already done this search |
48473171 | 63 | * |
b67daa72 | 64 | * @var bool |
48473171 CW |
65 | */ |
66 | protected $_done; | |
67 | ||
68 | /** | |
100fef9d | 69 | * What context are we being invoked from |
48473171 | 70 | * |
48473171 CW |
71 | * @var string |
72 | */ | |
73 | protected $_context = NULL; | |
74 | ||
7a3978aa FG |
75 | /** |
76 | * The list of tasks or actions that a searcher can perform on a result set. | |
77 | * | |
78 | * @var array | |
79 | */ | |
be2fb01f | 80 | protected $_taskList = []; |
7a3978aa | 81 | |
df60621b | 82 | /** |
83 | * Declare entity reference fields as they will need to be converted. | |
84 | * | |
85 | * The entity reference format looks like '2,3' whereas the Query object expects array(2, 3) | |
86 | * or array('IN' => array(2, 3). The latter is the one we are moving towards standardising on. | |
87 | * | |
88 | * @var array | |
89 | */ | |
be2fb01f | 90 | protected $entityReferenceFields = []; |
df60621b | 91 | |
7a3978aa FG |
92 | /** |
93 | * Builds the list of tasks or actions that a searcher can perform on a result set. | |
94 | * | |
95 | * To modify the task list, child classes should alter $this->_taskList, | |
96 | * preferably by extending this method. | |
97 | * | |
98 | * @return array | |
99 | */ | |
100 | protected function buildTaskList() { | |
101 | return $this->_taskList; | |
102 | } | |
103 | ||
e6dda67a | 104 | /** |
105 | * Metadata for fields on the search form. | |
106 | * | |
8c9caddc | 107 | * Instantiate with empty array for contact to prevent e-notices. |
108 | * | |
e6dda67a | 109 | * @var array |
110 | */ | |
8c9caddc | 111 | protected $searchFieldMetadata = ['Contact' => []]; |
e6dda67a | 112 | |
113 | /** | |
114 | * @return array | |
115 | */ | |
116 | public function getSearchFieldMetadata() { | |
117 | return $this->searchFieldMetadata; | |
118 | } | |
119 | ||
120 | /** | |
121 | * @param array $searchFieldMetadata | |
122 | */ | |
123 | public function addSearchFieldMetadata($searchFieldMetadata) { | |
124 | $this->searchFieldMetadata = array_merge($this->searchFieldMetadata, $searchFieldMetadata); | |
125 | } | |
126 | ||
8c9caddc | 127 | /** |
128 | * This virtual function is used to set the default values of various form elements. | |
129 | * | |
130 | * @return array|NULL | |
131 | * reference to the array of default values | |
132 | * @throws \Exception | |
133 | */ | |
134 | public function setDefaultValues() { | |
e8082ca5 | 135 | $defaults = (array) $this->_formValues; |
136 | foreach (['Contact', $this->getDefaultEntity()] as $entity) { | |
137 | $defaults = array_merge($this->getEntityDefaults($entity), $defaults); | |
138 | } | |
139 | return $defaults; | |
8c9caddc | 140 | } |
141 | ||
359fdb6f | 142 | /** |
143 | * Set the form values based on input and preliminary processing. | |
144 | */ | |
145 | protected function setFormValues() { | |
146 | if (!empty($_POST) && !$this->_force) { | |
147 | $this->_formValues = $this->controller->exportValues($this->_name); | |
148 | } | |
149 | $this->convertTextStringsToUseLikeOperator(); | |
150 | } | |
151 | ||
34197a55 | 152 | /** |
0955d6b9 | 153 | * Common buildForm tasks required by all searches. |
34197a55 | 154 | */ |
61b4d091 | 155 | public function buildQuickForm() { |
13d9bc82 | 156 | CRM_Core_Resources::singleton() |
562fda4b CW |
157 | ->addScriptFile('civicrm', 'js/crm.searchForm.js', 1, 'html-header') |
158 | ->addStyleFile('civicrm', 'css/searchForm.css', 1, 'html-header'); | |
3efb5b86 | 159 | |
be2fb01f CW |
160 | $this->addButtons([ |
161 | [ | |
3efb5b86 CW |
162 | 'type' => 'refresh', |
163 | 'name' => ts('Search'), | |
164 | 'isDefault' => TRUE, | |
be2fb01f CW |
165 | ], |
166 | ]); | |
8d36b801 | 167 | |
023e90c3 | 168 | $this->addClass('crm-search-form'); |
7a3978aa | 169 | |
7a3978aa FG |
170 | $tasks = $this->buildTaskList(); |
171 | $this->addTaskMenu($tasks); | |
8d36b801 CW |
172 | } |
173 | ||
e6dda67a | 174 | /** |
175 | * Add any fields described in metadata to the form. | |
176 | * | |
177 | * The goal is to describe all fields in metadata and handle from metadata rather | |
178 | * than existing ad hoc handling. | |
179 | */ | |
180 | public function addFormFieldsFromMetadata() { | |
2307be08 | 181 | $this->addFormRule(['CRM_Core_Form_Search', 'formRule'], $this); |
e6dda67a | 182 | $this->_action = CRM_Core_Action::ADVANCED; |
183 | foreach ($this->getSearchFieldMetadata() as $entity => $fields) { | |
184 | foreach ($fields as $fieldName => $fieldSpec) { | |
27cedb98 | 185 | if ($fieldSpec['type'] === CRM_Utils_Type::T_DATE || $fieldSpec['type'] === (CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME)) { |
1157f03f SL |
186 | $title = empty($fieldSpec['unique_title']) ? $fieldSpec['title'] : $fieldSpec['unique_title']; |
187 | $this->addDatePickerRange($fieldName, $title, ($fieldSpec['type'] === (CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME))); | |
0bcac7e7 | 188 | } |
189 | else { | |
8c9caddc | 190 | $props = ['entity' => $entity]; |
1157f03f SL |
191 | if (isset($fields[$fieldName]['unique_title'])) { |
192 | $props['label'] = $fields[$fieldName]['unique_title']; | |
193 | } | |
194 | elseif (isset($fields[$fieldName]['title'])) { | |
8c9caddc | 195 | $props['label'] = $fields[$fieldName]['title']; |
196 | } | |
359fdb6f | 197 | if (empty($fieldSpec['is_pseudofield'])) { |
198 | $this->addField($fieldName, $props); | |
199 | } | |
0bcac7e7 | 200 | } |
e6dda67a | 201 | } |
202 | } | |
203 | } | |
204 | ||
2307be08 | 205 | /** |
206 | * Global validation rules for the form. | |
207 | * | |
208 | * @param array $fields | |
209 | * Posted values of the form. | |
518fa0ee SL |
210 | * @param array $files |
211 | * @param object $form | |
2307be08 | 212 | * |
213 | * @return array | |
214 | * list of errors to be posted back to the form | |
215 | */ | |
216 | public static function formRule($fields, $files, $form) { | |
217 | $errors = []; | |
b9c90943 | 218 | if (!is_a($form, 'CRM_Core_Form_Search')) { |
219 | // So this gets hit with a form object when doing an activity date search from | |
220 | // advanced search, but a NULL object when doing a pledge search. | |
221 | return $errors; | |
222 | } | |
2307be08 | 223 | foreach ($form->getSearchFieldMetadata() as $entity => $spec) { |
224 | foreach ($spec as $fieldName => $fieldSpec) { | |
225 | if ($fieldSpec['type'] === CRM_Utils_Type::T_DATE || $fieldSpec['type'] === (CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME)) { | |
8a6fde27 | 226 | if (!empty($fields[$fieldName . '_high']) && !empty($fields[$fieldName . '_low']) && empty($fields[$fieldName . '_relative'])) { |
2307be08 | 227 | if (strtotime($fields[$fieldName . '_low']) > strtotime($fields[$fieldName . '_high'])) { |
228 | $errors[$fieldName . '_low'] = ts('%1: Please check that your date range is in correct chronological order.', [1 => $fieldSpec['title']]); | |
229 | } | |
230 | } | |
231 | } | |
232 | } | |
233 | } | |
234 | return $errors; | |
235 | } | |
236 | ||
e6dda67a | 237 | /** |
238 | * Get the validation rule to apply to a function. | |
239 | * | |
240 | * Alphanumeric is designed to always be safe & for now we just return | |
241 | * that but in future we can use tighter rules for types like int, bool etc. | |
242 | * | |
243 | * @param string $entity | |
244 | * @param string $fieldName | |
245 | * | |
246 | * @return string | |
247 | */ | |
248 | protected function getValidationTypeForField($entity, $fieldName) { | |
249 | switch ($this->getSearchFieldMetadata()[$entity][$fieldName]['type']) { | |
250 | case CRM_Utils_Type::T_BOOLEAN: | |
251 | return 'Boolean'; | |
252 | ||
253 | case CRM_Utils_Type::T_INT: | |
254 | return 'CommaSeparatedIntegers'; | |
255 | ||
27cedb98 | 256 | case CRM_Utils_Type::T_DATE: |
257 | case CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME: | |
258 | return 'Timestamp'; | |
259 | ||
e6dda67a | 260 | default: |
261 | return 'Alphanumeric'; | |
262 | } | |
263 | } | |
264 | ||
265 | /** | |
266 | * Get the defaults for the entity for any fields described in metadata. | |
267 | * | |
268 | * @param string $entity | |
269 | * | |
270 | * @return array | |
271 | */ | |
272 | protected function getEntityDefaults($entity) { | |
273 | $defaults = []; | |
e8082ca5 | 274 | foreach (CRM_Utils_Array::value($entity, $this->getSearchFieldMetadata(), []) as $fieldName => $fieldSpec) { |
275 | if (empty($_POST[$fieldName])) { | |
276 | $value = CRM_Utils_Request::retrieveValue($fieldName, $this->getValidationTypeForField($entity, $fieldName), NULL, NULL, 'GET'); | |
c5c17034 | 277 | if ($value !== NULL) { |
278 | $defaults[$fieldName] = $value; | |
e6dda67a | 279 | } |
27cedb98 | 280 | if ($fieldSpec['type'] === CRM_Utils_Type::T_DATE || ($fieldSpec['type'] === CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME)) { |
e8082ca5 | 281 | $low = CRM_Utils_Request::retrieveValue($fieldName . '_low', 'Timestamp', NULL, NULL, 'GET'); |
282 | $high = CRM_Utils_Request::retrieveValue($fieldName . '_high', 'Timestamp', NULL, NULL, 'GET'); | |
283 | if ($low !== NULL || $high !== NULL) { | |
c5c17034 | 284 | $defaults[$fieldName . '_relative'] = 0; |
285 | $defaults[$fieldName . '_low'] = $low ? date('Y-m-d H:i:s', strtotime($low)) : NULL; | |
286 | $defaults[$fieldName . '_high'] = $high ? date('Y-m-d H:i:s', strtotime($high)) : NULL; | |
27cedb98 | 287 | } |
288 | } | |
e6dda67a | 289 | } |
290 | } | |
291 | return $defaults; | |
292 | } | |
293 | ||
7123f88c | 294 | /** |
295 | * Convert any submitted text fields to use 'like' rather than '=' as the operator. | |
296 | * | |
297 | * This excludes any with options. | |
298 | * | |
299 | * Note this will only pick up fields declared via metadata. | |
300 | */ | |
301 | protected function convertTextStringsToUseLikeOperator() { | |
359fdb6f | 302 | foreach ($this->getSearchFieldMetadata() as $entity => $fields) { |
303 | foreach ($fields as $fieldName => $field) { | |
304 | if (!empty($this->_formValues[$fieldName]) && empty($field['options']) && empty($field['pseudoconstant'])) { | |
305 | if (in_array($field['type'], [CRM_Utils_Type::T_STRING, CRM_Utils_Type::T_TEXT])) { | |
306 | $this->_formValues[$fieldName] = ['LIKE' => CRM_Contact_BAO_Query::getWildCardedValue(TRUE, 'LIKE', $this->_formValues[$fieldName])]; | |
307 | } | |
7123f88c | 308 | } |
309 | } | |
310 | } | |
311 | } | |
312 | ||
8d36b801 | 313 | /** |
0955d6b9 | 314 | * Add checkboxes for each row plus a master checkbox. |
ad37ac8e | 315 | * |
316 | * @param array $rows | |
8d36b801 | 317 | */ |
00be9182 | 318 | public function addRowSelectors($rows) { |
be2fb01f | 319 | $this->addElement('checkbox', 'toggleSelect', NULL, NULL, ['class' => 'select-rows']); |
4126499f | 320 | if (!empty($rows)) { |
321 | foreach ($rows as $row) { | |
6eb91e49 | 322 | if (CRM_Utils_Array::value('checkbox', $row)) { |
be2fb01f | 323 | $this->addElement('checkbox', $row['checkbox'], NULL, NULL, ['class' => 'select-row']); |
6eb91e49 | 324 | } |
4126499f | 325 | } |
8d36b801 | 326 | } |
3efb5b86 | 327 | } |
34197a55 | 328 | |
44543184 | 329 | /** |
330 | * Add actions menu to search results form. | |
331 | * | |
332 | * @param array $tasks | |
333 | */ | |
334 | public function addTaskMenu($tasks) { | |
be2fb01f | 335 | $taskMetaData = []; |
44543184 | 336 | foreach ($tasks as $key => $task) { |
be2fb01f | 337 | $taskMetaData[$key] = ['title' => $task]; |
44543184 | 338 | } |
339 | parent::addTaskMenu($taskMetaData); | |
340 | } | |
341 | ||
e597fc33 DG |
342 | /** |
343 | * Add the sort-name field to the form. | |
344 | * | |
345 | * There is a setting to determine whether email is included in the search & we look this up to determine | |
346 | * which text to choose. | |
347 | * | |
348 | * Note that for translation purposes the full string works better than using 'prefix' hence we use override-able functions | |
349 | * to define the string. | |
350 | */ | |
351 | protected function addSortNameField() { | |
e8082ca5 | 352 | $title = civicrm_api3('setting', 'getvalue', ['name' => 'includeEmailInName', 'group' => 'Search Preferences']) ? $this->getSortNameLabelWithEmail() : $this->getSortNameLabelWithOutEmail(); |
e597fc33 DG |
353 | $this->addElement( |
354 | 'text', | |
355 | 'sort_name', | |
e8082ca5 | 356 | $title, |
e597fc33 DG |
357 | CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name') |
358 | ); | |
e8082ca5 | 359 | $this->searchFieldMetadata['Contact']['sort_name'] = ['name' => 'sort_name', 'title' => $title, 'type' => CRM_Utils_Type::T_STRING]; |
e597fc33 DG |
360 | } |
361 | ||
362 | /** | |
363 | * Get the label for the sortName field if email searching is on. | |
364 | * | |
365 | * (email searching is a setting under search preferences). | |
366 | * | |
367 | * @return string | |
368 | */ | |
369 | protected function getSortNameLabelWithEmail() { | |
370 | return ts('Name or Email'); | |
371 | } | |
372 | ||
373 | /** | |
374 | * Get the label for the sortName field if email searching is off. | |
375 | * | |
376 | * (email searching is a setting under search preferences). | |
377 | * | |
378 | * @return string | |
379 | */ | |
380 | protected function getSortNameLabelWithOutEmail() { | |
381 | return ts('Name'); | |
382 | } | |
383 | ||
2ee21eaa CW |
384 | /** |
385 | * Explicitly declare the form context for addField(). | |
386 | */ | |
387 | public function getDefaultContext() { | |
388 | return 'search'; | |
389 | } | |
390 | ||
0573fd28 | 391 | /** |
392 | * Add generic fields that specify the contact. | |
393 | */ | |
394 | protected function addContactSearchFields() { | |
240b0e65 | 395 | if (!$this->isFormInViewOrEditMode()) { |
396 | return; | |
397 | } | |
0573fd28 | 398 | $this->addSortNameField(); |
399 | ||
400 | $this->_group = CRM_Core_PseudoConstant::nestedGroup(); | |
401 | if ($this->_group) { | |
402 | $this->add('select', 'group', $this->getGroupLabel(), $this->_group, FALSE, | |
be2fb01f | 403 | [ |
0573fd28 | 404 | 'id' => 'group', |
405 | 'multiple' => 'multiple', | |
406 | 'class' => 'crm-select2', | |
be2fb01f | 407 | ] |
0573fd28 | 408 | ); |
409 | } | |
410 | ||
411 | $contactTags = CRM_Core_BAO_Tag::getTags(); | |
412 | if ($contactTags) { | |
413 | $this->add('select', 'contact_tags', $this->getTagLabel(), $contactTags, FALSE, | |
be2fb01f | 414 | [ |
0573fd28 | 415 | 'id' => 'contact_tags', |
416 | 'multiple' => 'multiple', | |
417 | 'class' => 'crm-select2', | |
be2fb01f | 418 | ] |
0573fd28 | 419 | ); |
420 | } | |
be2fb01f | 421 | $this->addField('contact_type', ['entity' => 'Contact']); |
0573fd28 | 422 | |
423 | if (CRM_Core_Permission::check('access deleted contacts') && Civi::settings()->get('contact_undelete')) { | |
424 | $this->addElement('checkbox', 'deleted_contacts', ts('Search in Trash') . '<br />' . ts('(deleted contacts)')); | |
425 | } | |
426 | ||
427 | } | |
428 | ||
09e47399 | 429 | /** |
430 | * we allow the controller to set force/reset externally, useful when we are being | |
431 | * driven by the wizard framework | |
432 | */ | |
64ffcefd | 433 | protected function loadStandardSearchOptionsFromUrl() { |
09e47399 | 434 | $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean'); |
435 | $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE); | |
436 | $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this); | |
437 | $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'search'); | |
438 | $this->_ssID = CRM_Utils_Request::retrieve('ssID', 'Positive', $this); | |
439 | $this->assign("context", $this->_context); | |
440 | } | |
441 | ||
442 | /** | |
443 | * Get user submitted values. | |
444 | * | |
445 | * Get it from controller only if form has been submitted, else preProcess has set this | |
446 | */ | |
64ffcefd | 447 | protected function loadFormValues() { |
09e47399 | 448 | if (!empty($_POST) && !$this->controller->isModal()) { |
449 | $this->_formValues = $this->controller->exportValues($this->_name); | |
450 | } | |
451 | else { | |
452 | $this->_formValues = $this->get('formValues'); | |
453 | } | |
454 | ||
455 | if (empty($this->_formValues)) { | |
456 | if (isset($this->_ssID)) { | |
457 | $this->_formValues = CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID); | |
458 | } | |
459 | } | |
460 | } | |
461 | ||
3efb5b86 | 462 | } |