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 * Function to actually build the form
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')
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 * @internal param array $fields posted values of the form
148 * @return array list of errors to be posted back to the form
152 static function formRule($values, $files, $self) {
153 if (!empty($values['addMore']) ||
!empty($values['addBlock'])) {
156 $fields = self
::fields();
157 $fld = CRM_Core_BAO_Mapping
::formattedFields($values, TRUE);
160 foreach ($fld as $k => $v) {
162 $errorMsg["operator[$v[3]][$v[4]]"] = ts("Please enter the operator.");
166 $v[2] = self
::checkArrayKeyEmpty($v[2]);
168 if (in_array($v[1], array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY')) &&
170 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please clear your value if you want to use %1 operator.', array(1 => $v[1]));
172 elseif (($v[0] == 'group' ||
$v[0] == 'tag') && !empty($v[2])) {
173 $grpId = array_keys($v[2]);
175 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
178 if (count($grpId) > 1) {
179 if ($v[1] != 'IN' && $v[1] != 'NOT IN') {
180 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
182 foreach ($grpId as $val) {
183 $error = CRM_Utils_Type
::validate($val, 'Integer', FALSE);
184 if ($error != $val) {
185 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter valid value.");
191 $error = CRM_Utils_Type
::validate($grpId[0], 'Integer', FALSE);
192 if ($error != $grpId[0]) {
193 $errorMsg["value[$v[3]][$v[4]]"] = ts('Please enter valid %1 id.', array(1 => $v[0]));
197 elseif (substr($v[0], 0, 7) === 'do_not_' or substr($v[0], 0, 3) === 'is_') {
201 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
204 $error = CRM_Utils_Type
::validate($v2[0], 'Integer', FALSE);
205 if ($error != $v2[0]) {
206 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
210 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
214 if (substr($v[0], 0, 7) == 'custom_') {
215 // Get rid of appended location type id
216 list($fieldKey) = explode('-', $v[0]);
217 $type = $fields[$fieldKey]['data_type'];
219 // hack to handle custom data of type state and country
220 if (in_array($type, array(
221 'Country', 'StateProvince'))) {
227 // FIXME: no idea at this point what to do with this,
228 // FIXME: but definitely needs fixing.
229 if (substr($v[0], 0, 13) == 'contribution_') {
230 $fldName = substr($v[0], 13);
233 $fldValue = CRM_Utils_Array
::value($fldName, $fields);
234 $fldType = CRM_Utils_Array
::value('type', $fldValue);
235 $type = CRM_Utils_Type
::typeToString($fldType);
236 // Check Empty values for Integer Or Boolean Or Date type For operators other than IS NULL and IS NOT NULL.
238 array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) {
239 if ((($type == 'Int' ||
$type == 'Boolean') && !trim($v[2])) && $v[2] != '0') {
240 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
242 elseif ($type == 'Date' && !trim($v[2])) {
243 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value.");
248 if ($type && empty($errorMsg)) {
249 // check for valid format while using IN Operator
251 $inVal = trim($v[2]);
252 //checking for format to avoid db errors
253 if ($type == 'Int') {
254 if (!preg_match('/^[(]([A-Za-z0-9\,]+)[)]$/', $inVal)) {
255 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
259 if (!(substr($inVal, 0, 1) == '(' && substr($inVal, -1, 1) == ')') && !preg_match('/^[(]([A-Za-z0-9åäöÅÄÖüÜœŒæÆøØ\,\s]+)[)]$/', $inVal)) {
260 $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 = 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() {}
305 public function &convertFormValues(&$formValues) {
306 return CRM_Core_BAO_Mapping
::formattedFields($formValues);
309 public function &returnProperties() {
310 return CRM_Core_BAO_Mapping
::returnProperties($this->_formValues
);
314 * Process the uploaded file
319 public function postProcess() {
320 $this->set('isAdvanced', '2');
321 $this->set('isSearchBuilder', '1');
322 $this->set('showSearchForm', FALSE);
324 $params = $this->controller
->exportValues($this->_name
);
325 if (!empty($params)) {
327 if (!empty($params['addBlock'])) {
328 $this->set('newBlock', $this->_blockCount
);
329 $this->_blockCount +
= 3;
330 $this->set('blockCount', $this->_blockCount
);
331 $this->set('showSearchForm', TRUE);
335 $addMore = CRM_Utils_Array
::value('addMore', $params);
336 for ($x = 1; $x <= $this->_blockCount
; $x++
) {
337 if (!empty($addMore[$x])) {
338 $this->set('newBlock', $x);
339 $this->_columnCount
[$x] = $this->_columnCount
[$x] +
5;
340 $this->set('columnCount', $this->_columnCount
);
341 $this->set('showSearchForm', TRUE);
345 $this->set('newBlock', NULL);
347 foreach ($params['mapper'] as $key => $value) {
348 foreach ($value as $k => $v) {
356 $this->set('newBlock', 1);
357 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/search/builder', '_qf_Builder_display=true'));
361 // get user submitted values
362 // get it from controller only if form has been submitted, else preProcess has set this
363 if (!empty($_POST)) {
364 $this->_formValues
= $this->controller
->exportValues($this->_name
);
366 // set the group if group is submitted
367 if (!empty($this->_formValues
['uf_group_id'])) {
368 $this->set('id', $this->_formValues
['uf_group_id']);
371 $this->set('id', '');
375 // we dont want to store the sortByCharacter in the formValue, it is more like
376 // a filter on the result set
377 // this filter is reset if we click on the search button
378 if ($this->_sortByCharacter
!== NULL && empty($_POST)) {
379 if (strtolower($this->_sortByCharacter
) == 'all') {
380 $this->_formValues
['sortByCharacter'] = NULL;
383 $this->_formValues
['sortByCharacter'] = $this->_sortByCharacter
;
387 $this->_sortByCharacter
= NULL;
390 $this->_params
= &$this->convertFormValues($this->_formValues
);
391 $this->_returnProperties
= &$this->returnProperties();
393 // CRM-10338 check if value is empty array
394 foreach ($this->_params
as $k => $v) {
395 $this->_params
[$k][2] = self
::checkArrayKeyEmpty($v[2]);
398 parent
::postProcess();
401 static function fields() {
402 $fields = array_merge(
403 CRM_Contact_BAO_Contact
::exportableFields('All', FALSE, TRUE),
404 CRM_Core_Component
::getQueryFields(),
405 CRM_Contact_BAO_Query_Hook
::singleton()->getFields(),
406 CRM_Activity_BAO_Activity
::exportableFields()
412 * CRM-9434 Hackish function to fetch fields with options.
413 * FIXME: When our core fields contain reliable metadata this will be much simpler.
414 * @return array: (string => string) key: field_name value: api entity name
415 * Note: options are fetched via ajax using the api "getoptions" method
417 static function fieldOptions() {
418 // Hack to add options not retrieved by getfields
419 // This list could go on and on, but it would be better to fix getfields
421 'group' => 'group_contact',
422 'tag' => 'entity_tag',
423 'on_hold' => 'yesno',
424 'is_bulkmail' => 'yesno',
425 'payment_instrument' => 'contribution',
426 'membership_status' => 'membership',
427 'membership_type' => 'membership',
428 'member_campaign_id' => 'membership',
429 'member_is_test' => 'yesno',
430 'member_is_pay_later' => 'yesno',
431 'is_override' => 'yesno',
433 $entities = array('contact', 'address', 'activity', 'participant', 'pledge', 'member', 'contribution');
434 CRM_Contact_BAO_Query_Hook
::singleton()->alterSearchBuilderOptions($entities, $options);
435 foreach ($entities as $entity) {
436 $fields = civicrm_api3($entity, 'getfields');
437 foreach ($fields['values'] as $field => $info) {
438 if (!empty($info['options']) ||
!empty($info['pseudoconstant']) ||
!empty($info['option_group_id'])) {
439 $options[$field] = $entity;
440 if (substr($field, -3) == '_id') {
441 $options[substr($field, 0, -3)] = $entity;
444 elseif (in_array(substr($field, 0, 3), array('is_', 'do_')) || CRM_Utils_Array
::value('data_type', $info) == 'Boolean') {
445 $options[$field] = 'yesno';
446 if ($entity != 'contact') {
447 $options[$entity . '_' . $field] = 'yesno';
450 elseif (strpos($field, '_is_')) {
451 $options[$field] = 'yesno';
460 * tags and groups use array keys for selection list.
461 * if using IS NULL/NOT NULL, an array with no array key is created
462 * convert that to simple null so processing can proceed
464 static function checkArrayKeyEmpty($val) {
465 if (is_array($val)) {
467 foreach ($val as $vk => $vv) {