Merge pull request #13270 from civicrm/5.9
[civicrm-core.git] / CRM / Core / Form / Search.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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 |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
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 |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Base class for most search forms
30 */
31 class CRM_Core_Form_Search extends CRM_Core_Form {
32
33 /**
34 * Are we forced to run a search
35 *
36 * @var int
37 */
38 protected $_force;
39
40 /**
41 * Name of search button
42 *
43 * @var string
44 */
45 protected $_searchButtonName;
46
47 /**
48 * Name of action button
49 *
50 * @var string
51 */
52 protected $_actionButtonName;
53
54 /**
55 * Form values that we will be using
56 *
57 * @var array
58 */
59 public $_formValues;
60
61 /**
62 * Have we already done this search
63 *
64 * @var boolean
65 */
66 protected $_done;
67
68 /**
69 * What context are we being invoked from
70 *
71 * @var string
72 */
73 protected $_context = NULL;
74
75 /**
76 * The list of tasks or actions that a searcher can perform on a result set.
77 *
78 * @var array
79 */
80 protected $_taskList = array();
81
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 */
90 protected $entityReferenceFields = array();
91
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
104 /**
105 * Metadata for fields on the search form.
106 *
107 * @var array
108 */
109 protected $searchFieldMetadata = [];
110
111 /**
112 * @return array
113 */
114 public function getSearchFieldMetadata() {
115 return $this->searchFieldMetadata;
116 }
117
118 /**
119 * @param array $searchFieldMetadata
120 */
121 public function addSearchFieldMetadata($searchFieldMetadata) {
122 $this->searchFieldMetadata = array_merge($this->searchFieldMetadata, $searchFieldMetadata);
123 }
124
125 /**
126 * Common buildForm tasks required by all searches.
127 */
128 public function buildQuickform() {
129 CRM_Core_Resources::singleton()
130 ->addScriptFile('civicrm', 'js/crm.searchForm.js', 1, 'html-header')
131 ->addStyleFile('civicrm', 'css/searchForm.css', 1, 'html-header');
132
133 $this->addButtons(array(
134 array(
135 'type' => 'refresh',
136 'name' => ts('Search'),
137 'isDefault' => TRUE,
138 ),
139 ));
140
141 $this->addClass('crm-search-form');
142
143 $tasks = $this->buildTaskList();
144 $this->addTaskMenu($tasks);
145 }
146
147 /**
148 * Add any fields described in metadata to the form.
149 *
150 * The goal is to describe all fields in metadata and handle from metadata rather
151 * than existing ad hoc handling.
152 */
153 public function addFormFieldsFromMetadata() {
154 $this->_action = CRM_Core_Action::ADVANCED;
155 foreach ($this->getSearchFieldMetadata() as $entity => $fields) {
156 foreach ($fields as $fieldName => $fieldSpec) {
157 $this->addField($fieldName, ['entity' => $entity]);
158 }
159 }
160 }
161
162 /**
163 * Get the validation rule to apply to a function.
164 *
165 * Alphanumeric is designed to always be safe & for now we just return
166 * that but in future we can use tighter rules for types like int, bool etc.
167 *
168 * @param string $entity
169 * @param string $fieldName
170 *
171 * @return string
172 */
173 protected function getValidationTypeForField($entity, $fieldName) {
174 switch ($this->getSearchFieldMetadata()[$entity][$fieldName]['type']) {
175 case CRM_Utils_Type::T_BOOLEAN:
176 return 'Boolean';
177
178 case CRM_Utils_Type::T_INT:
179 return 'CommaSeparatedIntegers';
180
181 default:
182 return 'Alphanumeric';
183 }
184 }
185
186 /**
187 * Get the defaults for the entity for any fields described in metadata.
188 *
189 * @param string $entity
190 *
191 * @return array
192 */
193 protected function getEntityDefaults($entity) {
194 $defaults = [];
195 foreach ($this->getSearchFieldMetadata()[$entity] as $fieldSpec) {
196 if (empty($_POST[$fieldSpec['name']])) {
197 $value = CRM_Utils_Request::retrieveValue($fieldSpec['name'], $this->getValidationTypeForField($entity, $fieldSpec['name']), FALSE, NULL, 'GET');
198 if ($value !== FALSE) {
199 $defaults[$fieldSpec['name']] = $value;
200 }
201 }
202 }
203 return $defaults;
204 }
205
206 /**
207 * Add checkboxes for each row plus a master checkbox.
208 *
209 * @param array $rows
210 */
211 public function addRowSelectors($rows) {
212 $this->addElement('checkbox', 'toggleSelect', NULL, NULL, array('class' => 'select-rows'));
213 if (!empty($rows)) {
214 foreach ($rows as $row) {
215 if (CRM_Utils_Array::value('checkbox', $row)) {
216 $this->addElement('checkbox', $row['checkbox'], NULL, NULL, array('class' => 'select-row'));
217 }
218 }
219 }
220 }
221
222 /**
223 * Add actions menu to search results form.
224 *
225 * @param array $tasks
226 */
227 public function addTaskMenu($tasks) {
228 $taskMetaData = array();
229 foreach ($tasks as $key => $task) {
230 $taskMetaData[$key] = array('title' => $task);
231 }
232 parent::addTaskMenu($taskMetaData);
233 }
234
235 /**
236 * Add the sort-name field to the form.
237 *
238 * There is a setting to determine whether email is included in the search & we look this up to determine
239 * which text to choose.
240 *
241 * Note that for translation purposes the full string works better than using 'prefix' hence we use override-able functions
242 * to define the string.
243 */
244 protected function addSortNameField() {
245 $this->addElement(
246 'text',
247 'sort_name',
248 civicrm_api3('setting', 'getvalue', array('name' => 'includeEmailInName', 'group' => 'Search Preferences')) ? $this->getSortNameLabelWithEmail() : $this->getSortNameLabelWithOutEmail(),
249 CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')
250 );
251 }
252
253 /**
254 * Get the label for the sortName field if email searching is on.
255 *
256 * (email searching is a setting under search preferences).
257 *
258 * @return string
259 */
260 protected function getSortNameLabelWithEmail() {
261 return ts('Name or Email');
262 }
263
264 /**
265 * Get the label for the sortName field if email searching is off.
266 *
267 * (email searching is a setting under search preferences).
268 *
269 * @return string
270 */
271 protected function getSortNameLabelWithOutEmail() {
272 return ts('Name');
273 }
274
275 /**
276 * Explicitly declare the form context for addField().
277 */
278 public function getDefaultContext() {
279 return 'search';
280 }
281
282 /**
283 * Add generic fields that specify the contact.
284 */
285 protected function addContactSearchFields() {
286 if (!$this->isFormInViewOrEditMode()) {
287 return;
288 }
289 $this->addSortNameField();
290
291 $this->_group = CRM_Core_PseudoConstant::nestedGroup();
292 if ($this->_group) {
293 $this->add('select', 'group', $this->getGroupLabel(), $this->_group, FALSE,
294 array(
295 'id' => 'group',
296 'multiple' => 'multiple',
297 'class' => 'crm-select2',
298 )
299 );
300 }
301
302 $contactTags = CRM_Core_BAO_Tag::getTags();
303 if ($contactTags) {
304 $this->add('select', 'contact_tags', $this->getTagLabel(), $contactTags, FALSE,
305 array(
306 'id' => 'contact_tags',
307 'multiple' => 'multiple',
308 'class' => 'crm-select2',
309 )
310 );
311 }
312 $this->addField('contact_type', array('entity' => 'Contact'));
313
314 if (CRM_Core_Permission::check('access deleted contacts') && Civi::settings()->get('contact_undelete')) {
315 $this->addElement('checkbox', 'deleted_contacts', ts('Search in Trash') . '<br />' . ts('(deleted contacts)'));
316 }
317
318 }
319
320 /**
321 * we allow the controller to set force/reset externally, useful when we are being
322 * driven by the wizard framework
323 */
324 protected function loadStandardSearchOptionsFromUrl() {
325 $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean');
326 $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE);
327 $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this);
328 $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'search');
329 $this->_ssID = CRM_Utils_Request::retrieve('ssID', 'Positive', $this);
330 $this->assign("context", $this->_context);
331 }
332
333 /**
334 * Get user submitted values.
335 *
336 * Get it from controller only if form has been submitted, else preProcess has set this
337 */
338 protected function loadFormValues() {
339 if (!empty($_POST) && !$this->controller->isModal()) {
340 $this->_formValues = $this->controller->exportValues($this->_name);
341 }
342 else {
343 $this->_formValues = $this->get('formValues');
344 }
345
346 if (empty($this->_formValues)) {
347 if (isset($this->_ssID)) {
348 $this->_formValues = CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID);
349 }
350 }
351 }
352
353 }