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', 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 * @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 a 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') && !is_array($v[2]) && !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 if (!is_array($v[2])) {
252 $inVal = trim($v[2]);
253 //checking for format to avoid db errors
254 if ($type == 'Int') {
255 if (!preg_match('/^[(]([A-Za-z0-9\,]+)[)]$/', $inVal)) {
256 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
260 if (!(substr($inVal, 0, 1) == '(' && substr($inVal, -1, 1) == ')') && !preg_match('/^[(]([A-Za-z0-9åäöÅÄÖüÜœŒæÆøØ\,\s]+)[)]$/', $inVal)) {
261 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter correct Data (in valid format).");
266 // Validate each value in parenthesis to avoid db errors
267 if (empty($errorMsg)) {
268 $parenValues = array();
269 $parenValues = is_array($v[2]) ?
$v[2] : explode(',', trim($inVal, "(..)"));
270 foreach ($parenValues as $val) {
272 if (!$val && $val != '0') {
273 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter the values correctly.");
275 if (empty($errorMsg)) {
276 $error = CRM_Utils_Type
::validate($val, $type, FALSE);
277 if ($error != $val) {
278 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
284 elseif (trim($v[2])) {
285 //else check value for rest of the Operators
286 $error = CRM_Utils_Type
::validate($v[2], $type, FALSE);
287 if ($error != $v[2]) {
288 $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value.");
296 if (!empty($errorMsg)) {
297 $self->set('showSearchForm', TRUE);
298 $self->assign('rows', NULL);
305 public function normalizeFormValues() {}
312 public function convertFormValues(&$formValues) {
313 return CRM_Core_BAO_Mapping
::formattedFields($formValues);
319 public function &returnProperties() {
320 return CRM_Core_BAO_Mapping
::returnProperties($this->_formValues
);
324 * Process the uploaded file
329 public function postProcess() {
330 $this->set('isAdvanced', '2');
331 $this->set('isSearchBuilder', '1');
332 $this->set('showSearchForm', FALSE);
334 $params = $this->controller
->exportValues($this->_name
);
335 if (!empty($params)) {
337 if (!empty($params['addBlock'])) {
338 $this->set('newBlock', $this->_blockCount
);
339 $this->_blockCount +
= 3;
340 $this->set('blockCount', $this->_blockCount
);
341 $this->set('showSearchForm', TRUE);
345 $addMore = CRM_Utils_Array
::value('addMore', $params);
346 for ($x = 1; $x <= $this->_blockCount
; $x++
) {
347 if (!empty($addMore[$x])) {
348 $this->set('newBlock', $x);
349 $this->_columnCount
[$x] = $this->_columnCount
[$x] +
5;
350 $this->set('columnCount', $this->_columnCount
);
351 $this->set('showSearchForm', TRUE);
355 $this->set('newBlock', NULL);
357 foreach ($params['mapper'] as $key => $value) {
358 foreach ($value as $k => $v) {
366 $this->set('newBlock', 1);
367 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/search/builder', '_qf_Builder_display=true'));
371 // get user submitted values
372 // get it from controller only if form has been submitted, else preProcess has set this
373 if (!empty($_POST)) {
374 $this->_formValues
= $this->controller
->exportValues($this->_name
);
376 // set the group if group is submitted
377 if (!empty($this->_formValues
['uf_group_id'])) {
378 $this->set('id', $this->_formValues
['uf_group_id']);
381 $this->set('id', '');
385 // we dont want to store the sortByCharacter in the formValue, it is more like
386 // a filter on the result set
387 // this filter is reset if we click on the search button
388 if ($this->_sortByCharacter
!== NULL && empty($_POST)) {
389 if (strtolower($this->_sortByCharacter
) == 'all') {
390 $this->_formValues
['sortByCharacter'] = NULL;
393 $this->_formValues
['sortByCharacter'] = $this->_sortByCharacter
;
397 $this->_sortByCharacter
= NULL;
400 $this->_params
= $this->convertFormValues($this->_formValues
);
401 $this->_returnProperties
= &$this->returnProperties();
403 // CRM-10338 check if value is empty array
404 foreach ($this->_params
as $k => $v) {
405 $this->_params
[$k][2] = self
::checkArrayKeyEmpty($v[2]);
408 parent
::postProcess();
414 static function fields() {
415 $fields = array_merge(
416 CRM_Contact_BAO_Contact
::exportableFields('All', FALSE, TRUE),
417 CRM_Core_Component
::getQueryFields(),
418 CRM_Contact_BAO_Query_Hook
::singleton()->getFields(),
419 CRM_Activity_BAO_Activity
::exportableFields()
425 * CRM-9434 Hackish function to fetch fields with options.
426 * FIXME: When our core fields contain reliable metadata this will be much simpler.
427 * @return array: (string => string) key: field_name value: api entity name
428 * Note: options are fetched via ajax using the api "getoptions" method
430 static function fieldOptions() {
431 // Hack to add options not retrieved by getfields
432 // This list could go on and on, but it would be better to fix getfields
434 'group' => 'group_contact',
435 'tag' => 'entity_tag',
436 'on_hold' => 'yesno',
437 'is_bulkmail' => 'yesno',
438 'payment_instrument' => 'contribution',
439 'membership_status' => 'membership',
440 'membership_type' => 'membership',
441 'member_campaign_id' => 'membership',
442 'member_is_test' => 'yesno',
443 'member_is_pay_later' => 'yesno',
444 'is_override' => 'yesno',
446 $entities = array('contact', 'address', 'activity', 'participant', 'pledge', 'member', 'contribution');
447 CRM_Contact_BAO_Query_Hook
::singleton()->alterSearchBuilderOptions($entities, $options);
448 foreach ($entities as $entity) {
449 $fields = civicrm_api3($entity, 'getfields');
450 foreach ($fields['values'] as $field => $info) {
451 if (!empty($info['options']) ||
!empty($info['pseudoconstant']) ||
!empty($info['option_group_id'])) {
452 $options[$field] = $entity;
453 // Hack for when search field doesn't match db field - e.g. "country" instead of "country_id"
454 if (substr($field, -3) == '_id') {
455 $options[substr($field, 0, -3)] = $entity;
458 elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) {
459 $options[$field] = $entity;
461 elseif (in_array(substr($field, 0, 3), array('is_', 'do_')) || CRM_Utils_Array
::value('data_type', $info) == 'Boolean') {
462 $options[$field] = 'yesno';
463 if ($entity != 'contact') {
464 $options[$entity . '_' . $field] = 'yesno';
467 elseif (strpos($field, '_is_')) {
468 $options[$field] = 'yesno';
477 * tags and groups use array keys for selection list.
478 * if using IS NULL/NOT NULL, an array with no array key is created
479 * convert that to simple null so processing can proceed
481 static function checkArrayKeyEmpty($val) {
482 if (is_array($val)) {
484 foreach ($val as $vk => $vv) {