3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
37 * This class if for search builder processing
39 class CRM_Contact_Form_Search_Builder
extends CRM_Contact_Form_Search
{
42 * Number of columns in where
50 * Number of blocks to be shown
58 * Build the form object
63 public function preProcess() {
64 $this->set('searchFormName', 'Builder');
66 $this->set('context', 'builder');
69 // Get the block count
70 $this->_blockCount
= $this->get('blockCount');
71 // Initialize new form
72 if (!$this->_blockCount
) {
73 $this->_blockCount
= 4;
74 $this->set('newBlock', 1);
77 //get the column count
78 $this->_columnCount
= $this->get('columnCount');
80 for ($i = 1; $i < $this->_blockCount
; $i++
) {
81 if (empty($this->_columnCount
[$i])) {
82 $this->_columnCount
[$i] = 5;
86 $this->_loadedMappingId
= $this->get('savedMapping');
88 if ($this->get('showSearchForm')) {
89 $this->assign('showSearchForm', TRUE);
92 $this->assign('showSearchForm', FALSE);
96 public function buildQuickForm() {
97 $fields = self
::fields();
98 // Get fields of type date
99 // FIXME: This is a hack until our fields contain this meta-data
100 $dateFields = array();
101 foreach ($fields as $name => $field) {
102 if (strpos($name, '_date') || CRM_Utils_Array
::value('data_type', $field) == 'Date') {
103 $dateFields[] = $name;
107 CRM_Core_Resources
::singleton()
108 ->addScriptFile('civicrm', 'templates/CRM/Contact/Form/Search/Builder.js', 1, 'html-header')
110 'searchBuilder' => array(
111 // Index of newly added/expanded block (1-based index)
112 'newBlock' => $this->get('newBlock'),
113 'dateFields' => $dateFields,
114 'fieldOptions' => self
::fieldOptions(),
117 //get the saved search mapping id
120 $mappingId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_SavedSearch', $this->_ssID
, 'mapping_id');
123 CRM_Core_BAO_Mapping
::buildMappingForm($this, 'Search Builder', $mappingId, $this->_columnCount
, $this->_blockCount
);
125 parent
::buildQuickForm();
129 * Add local and global form rules
135 function addRules() {
136 $this->addFormRule(array('CRM_Contact_Form_Search_Builder', 'formRule'), $this);
140 * Global validation rules for the form
146 * @return array list of errors to be posted back to the form
150 static function formRule($values, $files, $self) {
151 if (!empty($values['addMore']) ||
!empty($values['addBlock'])) {
154 $fields = self
::fields();
155 $fld = CRM_Core_BAO_Mapping
::formattedFields($values, TRUE);
158 foreach ($fld as $k => $v) {
160 $errorMsg["operator[$v[3]][$v[4]]"] = ts("Please enter the operator.");
164 $v[2] = self
::checkArrayKeyEmpty($v[2]);
166 if (in_array($v[1], array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY')) &&
168 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please clear your value if you want to use %1 operator.', array(1 => $v[1]));
170 elseif (($v[0] == 'group' ||
$v[0] == 'tag') && !empty($v[2])) {
171 $grpId = array_keys($v[2]);
173 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
176 if (count($grpId) > 1) {
177 if ($v[1] != 'IN' && $v[1] != 'NOT IN') {
178 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
180 foreach ($grpId as $val) {
181 $error = CRM_Utils_Type
::validate($val, 'Integer', FALSE);
182 if ($error != $val) {
183 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
189 $error = CRM_Utils_Type
::validate($grpId[0], 'Integer', FALSE);
190 if ($error != $grpId[0]) {
191 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please enter valid %1 id.', array(1 => $v[0]));
195 elseif (substr($v[0], 0, 7) === 'do_not_' or substr($v[0], 0, 3) === 'is_') {
199 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
202 $error = CRM_Utils_Type
::validate($v2[0], 'Integer', FALSE);
203 if ($error != $v2[0]) {
204 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
208 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
212 if (substr($v[0], 0, 7) == 'custom_') {
213 // Get rid of appended location type id
214 list($fieldKey) = explode('-', $v[0]);
215 $type = $fields[$fieldKey]['data_type'];
217 // hack to handle custom data of type state and country
218 if (in_array($type, array(
219 'Country', 'StateProvince'))) {
225 // FIXME: no idea at this point what to do with this,
226 // FIXME: but definitely needs fixing.
227 if (substr($v[0], 0, 13) == 'contribution_') {
228 $fldName = substr($v[0], 13);
231 $fldValue = CRM_Utils_Array
::value($fldName, $fields);
232 $fldType = CRM_Utils_Array
::value('type', $fldValue);
233 $type = CRM_Utils_Type
::typeToString($fldType);
234 // Check Empty values for Integer Or Boolean Or Date type For operators other than IS NULL and IS NOT NULL.
236 array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) {
237 if ((($type == 'Int' ||
$type == 'Boolean') && !is_array($v[2]) && !trim($v[2])) && $v[2] != '0') {
238 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
240 elseif ($type == 'Date' && !trim($v[2])) {
241 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
246 if ($type && empty($errorMsg)) {
247 // check for valid format while using IN Operator
249 if (!is_array($v[2])) {
250 $inVal = trim($v[2]);
251 //checking for format to avoid db errors
252 if ($type == 'Int') {
253 if (!preg_match('/^[(]([A-Za-z0-9\,]+)[)]$/', $inVal)) {
254 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
258 if (!(substr($inVal, 0, 1) == '(' && substr($inVal, -1, 1) == ')') && !preg_match('/^[(]([A-Za-z0-9åäöÅÄÖüÜœŒæÆøØ\,\s]+)[)]$/', $inVal)) {
259 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
264 // Validate each value in parenthesis to avoid db errors
265 if (empty($errorMsg)) {
266 $parenValues = array();
267 $parenValues = is_array($v[2]) ?
$v[2] : explode(',', trim($inVal, "(..)"));
268 foreach ($parenValues as $val) {
270 if (!$val && $val != '0') {
271 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter the values correctly.");
273 if (empty($errorMsg)) {
274 $error = CRM_Utils_Type
::validate($val, $type, FALSE);
275 if ($error != $val) {
276 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
282 elseif (trim($v[2])) {
283 //else check value for rest of the Operators
284 $error = CRM_Utils_Type
::validate($v[2], $type, FALSE);
285 if ($error != $v[2]) {
286 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
294 if (!empty($errorMsg)) {
295 $self->set('showSearchForm', TRUE);
296 $self->assign('rows', NULL);
303 public function normalizeFormValues() {}
310 public function convertFormValues(&$formValues) {
311 return CRM_Core_BAO_Mapping
::formattedFields($formValues);
317 public function &returnProperties() {
318 return CRM_Core_BAO_Mapping
::returnProperties($this->_formValues
);
322 * Process the uploaded file
327 public function postProcess() {
328 $this->set('isAdvanced', '2');
329 $this->set('isSearchBuilder', '1');
330 $this->set('showSearchForm', FALSE);
332 $params = $this->controller
->exportValues($this->_name
);
333 if (!empty($params)) {
335 if (!empty($params['addBlock'])) {
336 $this->set('newBlock', $this->_blockCount
);
337 $this->_blockCount +
= 3;
338 $this->set('blockCount', $this->_blockCount
);
339 $this->set('showSearchForm', TRUE);
343 $addMore = CRM_Utils_Array
::value('addMore', $params);
344 for ($x = 1; $x <= $this->_blockCount
; $x++
) {
345 if (!empty($addMore[$x])) {
346 $this->set('newBlock', $x);
347 $this->_columnCount
[$x] = $this->_columnCount
[$x] +
5;
348 $this->set('columnCount', $this->_columnCount
);
349 $this->set('showSearchForm', TRUE);
353 $this->set('newBlock', NULL);
355 foreach ($params['mapper'] as $key => $value) {
356 foreach ($value as $k => $v) {
364 $this->set('newBlock', 1);
365 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/search/builder', '_qf_Builder_display=true'));
369 // get user submitted values
370 // get it from controller only if form has been submitted, else preProcess has set this
371 if (!empty($_POST)) {
372 $this->_formValues
= $this->controller
->exportValues($this->_name
);
374 // set the group if group is submitted
375 if (!empty($this->_formValues
['uf_group_id'])) {
376 $this->set('id', $this->_formValues
['uf_group_id']);
379 $this->set('id', '');
383 // we dont want to store the sortByCharacter in the formValue, it is more like
384 // a filter on the result set
385 // this filter is reset if we click on the search button
386 if ($this->_sortByCharacter
!== NULL && empty($_POST)) {
387 if (strtolower($this->_sortByCharacter
) == 'all') {
388 $this->_formValues
['sortByCharacter'] = NULL;
391 $this->_formValues
['sortByCharacter'] = $this->_sortByCharacter
;
395 $this->_sortByCharacter
= NULL;
398 $this->_params
= $this->convertFormValues($this->_formValues
);
399 $this->_returnProperties
= &$this->returnProperties();
401 // CRM-10338 check if value is empty array
402 foreach ($this->_params
as $k => $v) {
403 $this->_params
[$k][2] = self
::checkArrayKeyEmpty($v[2]);
406 parent
::postProcess();
412 static function fields() {
413 $fields = array_merge(
414 CRM_Contact_BAO_Contact
::exportableFields('All', FALSE, TRUE),
415 CRM_Core_Component
::getQueryFields(),
416 CRM_Contact_BAO_Query_Hook
::singleton()->getFields(),
417 CRM_Activity_BAO_Activity
::exportableFields()
423 * CRM-9434 Hackish function to fetch fields with options.
424 * FIXME: When our core fields contain reliable metadata this will be much simpler.
425 * @return array: (string => string) key: field_name value: api entity name
426 * Note: options are fetched via ajax using the api "getoptions" method
428 static function fieldOptions() {
429 // Hack to add options not retrieved by getfields
430 // This list could go on and on, but it would be better to fix getfields
432 'group' => 'group_contact',
433 'tag' => 'entity_tag',
434 'on_hold' => 'yesno',
435 'is_bulkmail' => 'yesno',
436 'payment_instrument' => 'contribution',
437 'membership_status' => 'membership',
438 'membership_type' => 'membership',
439 'member_campaign_id' => 'membership',
440 'member_is_test' => 'yesno',
441 'member_is_pay_later' => 'yesno',
442 'is_override' => 'yesno',
444 $entities = array('contact', 'address', 'activity', 'participant', 'pledge', 'member', 'contribution', 'case', 'grant');
445 CRM_Contact_BAO_Query_Hook
::singleton()->alterSearchBuilderOptions($entities, $options);
446 foreach ($entities as $entity) {
447 $fields = civicrm_api3($entity, 'getfields');
448 foreach ($fields['values'] as $field => $info) {
449 if (!empty($info['options']) ||
!empty($info['pseudoconstant']) ||
!empty($info['option_group_id'])) {
450 $options[$field] = $entity;
451 // Hack for when search field doesn't match db field - e.g. "country" instead of "country_id"
452 if (substr($field, -3) == '_id') {
453 $options[substr($field, 0, -3)] = $entity;
456 elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) {
457 $options[$field] = $entity;
459 elseif (in_array(substr($field, 0, 3), array('is_', 'do_')) || CRM_Utils_Array
::value('data_type', $info) == 'Boolean') {
460 $options[$field] = 'yesno';
461 if ($entity != 'contact') {
462 $options[$entity . '_' . $field] = 'yesno';
465 elseif (strpos($field, '_is_')) {
466 $options[$field] = 'yesno';
475 * tags and groups use array keys for selection list.
476 * if using IS NULL/NOT NULL, an array with no array key is created
477 * convert that to simple null so processing can proceed
479 static function checkArrayKeyEmpty($val) {
480 if (is_array($val)) {
482 foreach ($val as $vk => $vv) {