3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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-2015
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.
49 * Number of blocks to be shown.
56 * Build the form object.
60 public function preProcess() {
61 $this->set('searchFormName', 'Builder');
63 $this->set('context', 'builder');
66 // Get the block count
67 $this->_blockCount
= $this->get('blockCount');
68 // Initialize new form
69 if (!$this->_blockCount
) {
70 $this->_blockCount
= 4;
71 $this->set('newBlock', 1);
74 //get the column count
75 $this->_columnCount
= $this->get('columnCount');
77 for ($i = 1; $i < $this->_blockCount
; $i++
) {
78 if (empty($this->_columnCount
[$i])) {
79 $this->_columnCount
[$i] = 5;
83 $this->_loadedMappingId
= $this->get('savedMapping');
85 if ($this->get('showSearchForm')) {
86 $this->assign('showSearchForm', TRUE);
89 $this->assign('showSearchForm', FALSE);
93 public function buildQuickForm() {
94 $fields = self
::fields();
95 // Get fields of type date
96 // FIXME: This is a hack until our fields contain this meta-data
97 $dateFields = array();
98 foreach ($fields as $name => $field) {
99 if (strpos($name, '_date') || CRM_Utils_Array
::value('data_type', $field) == 'Date') {
100 $dateFields[] = $name;
104 CRM_Core_Resources
::singleton()
105 ->addScriptFile('civicrm', 'templates/CRM/Contact/Form/Search/Builder.js', 1, 'html-header')
107 'searchBuilder' => array(
108 // Index of newly added/expanded block (1-based index)
109 'newBlock' => $this->get('newBlock'),
110 'dateFields' => $dateFields,
111 'fieldOptions' => self
::fieldOptions(),
114 //get the saved search mapping id
117 $mappingId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_SavedSearch', $this->_ssID
, 'mapping_id');
120 CRM_Core_BAO_Mapping
::buildMappingForm($this, 'Search Builder', $mappingId, $this->_columnCount
, $this->_blockCount
);
122 parent
::buildQuickForm();
126 * Add local and global form rules.
131 public function addRules() {
132 $this->addFormRule(array('CRM_Contact_Form_Search_Builder', 'formRule'), $this);
136 * Global validation rules for the form.
143 * list of errors to be posted back to the form
145 public static function formRule($values, $files, $self) {
146 if (!empty($values['addMore']) ||
!empty($values['addBlock'])) {
149 $fields = self
::fields();
150 $fld = CRM_Core_BAO_Mapping
::formattedFields($values, TRUE);
153 foreach ($fld as $k => $v) {
155 $errorMsg["operator[$v[3]][$v[4]]"] = ts("Please enter the operator.");
159 $v[2] = self
::checkArrayKeyEmpty($v[2]);
161 if (in_array($v[1], array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY')) &&
164 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please clear your value if you want to use %1 operator.', array(1 => $v[1]));
166 elseif (($v[0] == 'group' ||
$v[0] == 'tag') && !empty($v[2])) {
167 $grpId = array_keys($v[2]);
169 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
172 if (count($grpId) > 1) {
173 if ($v[1] != 'IN' && $v[1] != 'NOT IN') {
174 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
176 foreach ($grpId as $val) {
177 $error = CRM_Utils_Type
::validate($val, 'Integer', FALSE);
178 if ($error != $val) {
179 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
185 $error = CRM_Utils_Type
::validate($grpId[0], 'Integer', FALSE);
186 if ($error != $grpId[0]) {
187 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please enter valid %1 id.', array(1 => $v[0]));
191 elseif (substr($v[0], 0, 7) === 'do_not_' or substr($v[0], 0, 3) === 'is_') {
195 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
198 $error = CRM_Utils_Type
::validate($v2[0], 'Integer', FALSE);
199 if ($error != $v2[0]) {
200 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
204 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
208 if (substr($v[0], 0, 7) == 'custom_') {
209 // Get rid of appended location type id
210 list($fieldKey) = explode('-', $v[0]);
211 $type = $fields[$fieldKey]['data_type'];
213 // hack to handle custom data of type state and country
214 if (in_array($type, array(
223 // FIXME: no idea at this point what to do with this,
224 // FIXME: but definitely needs fixing.
225 if (substr($v[0], 0, 13) == 'contribution_') {
226 $fldName = substr($v[0], 13);
229 $fldValue = CRM_Utils_Array
::value($fldName, $fields);
230 $fldType = CRM_Utils_Array
::value('type', $fldValue);
231 $type = CRM_Utils_Type
::typeToString($fldType);
232 // Check Empty values for Integer Or Boolean Or Date type For operators other than IS NULL and IS NOT NULL.
234 array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))
236 if ((($type == 'Int' ||
$type == 'Boolean') && !is_array($v[2]) && !trim($v[2])) && $v[2] != '0') {
237 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
239 elseif ($type == 'Date' && !trim($v[2])) {
240 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
245 if ($type && empty($errorMsg)) {
246 // check for valid format while using IN Operator
248 if (!is_array($v[2])) {
249 $inVal = trim($v[2]);
250 //checking for format to avoid db errors
251 if ($type == 'Int') {
252 if (!preg_match('/^[(]([A-Za-z0-9\,]+)[)]$/', $inVal)) {
253 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
257 if (!(substr($inVal, 0, 1) == '(' && substr($inVal, -1, 1) == ')') && !preg_match('/^[(]([A-Za-z0-9åäöÅÄÖüÜœŒæÆøØ\,\s]+)[)]$/', $inVal)) {
258 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
263 // Validate each value in parenthesis to avoid db errors
264 if (empty($errorMsg)) {
265 $parenValues = array();
266 $parenValues = is_array($v[2]) ?
$v[2] : explode(',', trim($inVal, "(..)"));
267 foreach ($parenValues as $val) {
269 if (!$val && $val != '0') {
270 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter the values correctly.");
272 if (empty($errorMsg)) {
273 $error = CRM_Utils_Type
::validate($val, $type, FALSE);
274 if ($error != $val) {
275 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
281 elseif (trim($v[2])) {
282 //else check value for rest of the Operators
283 $error = CRM_Utils_Type
::validate($v[2], $type, FALSE);
284 if ($error != $v[2]) {
285 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
293 if (!empty($errorMsg)) {
294 $self->set('showSearchForm', TRUE);
295 $self->assign('rows', NULL);
302 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.
326 public function postProcess() {
327 $this->set('isAdvanced', '2');
328 $this->set('isSearchBuilder', '1');
329 $this->set('showSearchForm', FALSE);
331 $params = $this->controller
->exportValues($this->_name
);
332 if (!empty($params)) {
334 if (!empty($params['addBlock'])) {
335 $this->set('newBlock', $this->_blockCount
);
336 $this->_blockCount +
= 3;
337 $this->set('blockCount', $this->_blockCount
);
338 $this->set('showSearchForm', TRUE);
342 $addMore = CRM_Utils_Array
::value('addMore', $params);
343 for ($x = 1; $x <= $this->_blockCount
; $x++
) {
344 if (!empty($addMore[$x])) {
345 $this->set('newBlock', $x);
346 $this->_columnCount
[$x] = $this->_columnCount
[$x] +
5;
347 $this->set('columnCount', $this->_columnCount
);
348 $this->set('showSearchForm', TRUE);
352 $this->set('newBlock', NULL);
354 foreach ($params['mapper'] as $key => $value) {
355 foreach ($value as $k => $v) {
363 $this->set('newBlock', 1);
364 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/search/builder', '_qf_Builder_display=true'));
368 // get user submitted values
369 // get it from controller only if form has been submitted, else preProcess has set this
370 if (!empty($_POST)) {
371 $this->_formValues
= $this->controller
->exportValues($this->_name
);
373 // set the group if group is submitted
374 if (!empty($this->_formValues
['uf_group_id'])) {
375 $this->set('id', $this->_formValues
['uf_group_id']);
378 $this->set('id', '');
382 // we dont want to store the sortByCharacter in the formValue, it is more like
383 // a filter on the result set
384 // this filter is reset if we click on the search button
385 if ($this->_sortByCharacter
!== NULL && empty($_POST)) {
386 if (strtolower($this->_sortByCharacter
) == 'all') {
387 $this->_formValues
['sortByCharacter'] = NULL;
390 $this->_formValues
['sortByCharacter'] = $this->_sortByCharacter
;
394 $this->_sortByCharacter
= NULL;
397 $this->_params
= $this->convertFormValues($this->_formValues
);
398 $this->_returnProperties
= &$this->returnProperties();
400 // CRM-10338 check if value is empty array
401 foreach ($this->_params
as $k => $v) {
402 $this->_params
[$k][2] = self
::checkArrayKeyEmpty($v[2]);
405 parent
::postProcess();
411 public static function fields() {
412 $fields = array_merge(
413 CRM_Contact_BAO_Contact
::exportableFields('All', FALSE, TRUE),
414 CRM_Core_Component
::getQueryFields(),
415 CRM_Contact_BAO_Query_Hook
::singleton()->getFields(),
416 CRM_Activity_BAO_Activity
::exportableFields()
422 * CRM-9434 Hackish function to fetch fields with options.
423 * FIXME: When our core fields contain reliable metadata this will be much simpler.
425 * (string => string) key: field_name value: api entity name
426 * Note: options are fetched via ajax using the api "getoptions" method
428 public 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',
455 CRM_Contact_BAO_Query_Hook
::singleton()->alterSearchBuilderOptions($entities, $options);
456 foreach ($entities as $entity) {
457 $fields = civicrm_api3($entity, 'getfields');
458 foreach ($fields['values'] as $field => $info) {
459 if (!empty($info['options']) ||
!empty($info['pseudoconstant']) ||
!empty($info['option_group_id'])) {
460 $options[$field] = $entity;
461 // Hack for when search field doesn't match db field - e.g. "country" instead of "country_id"
462 if (substr($field, -3) == '_id') {
463 $options[substr($field, 0, -3)] = $entity;
466 elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) {
467 $options[$field] = $entity;
469 elseif (in_array(substr($field, 0, 3), array(
472 )) || CRM_Utils_Array
::value('data_type', $info) == 'Boolean'
474 $options[$field] = 'yesno';
475 if ($entity != 'contact') {
476 $options[$entity . '_' . $field] = 'yesno';
479 elseif (strpos($field, '_is_')) {
480 $options[$field] = 'yesno';
489 * tags and groups use array keys for selection list.
490 * if using IS NULL/NOT NULL, an array with no array key is created
491 * convert that to simple NULL so processing can proceed
493 public static function checkArrayKeyEmpty($val) {
494 if (is_array($val)) {
496 foreach ($val as $vk => $vv) {