Merge pull request #4291 from mrpaulc/CRM-12920
[civicrm-core.git] / CRM / Core / BAO / CustomField.php
index 854831b33f9ae7499a8962786f88d8005622be04..da6671bb40205f80197c65f6a9ded5f1abc03f63 100644 (file)
@@ -285,9 +285,10 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     // make sure all values are present in the object for further processing
     $customField->find(TRUE);
 
+    $triggerRebuild = CRM_Utils_Array::value('triggerRebuild', $params, TRUE);
     //create/drop the index when we toggle the is_searchable flag
     if (!empty($params['id'])) {
-      self::createField($customField, 'modify', $indexExist);
+      self::createField($customField, 'modify', $indexExist, $triggerRebuild);
     }
     else {
       if (!isset($origParams['column_name'])) {
@@ -299,7 +300,8 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       // make sure all values are present in the object
       $customField->find(TRUE);
 
-      self::createField($customField, 'add');
+      $indexExist = FALSE;
+      self::createField($customField, 'add', $indexExist, $triggerRebuild);
     }
 
     // complete transaction
@@ -409,11 +411,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       }
     }
 
-    if ($customDataSubType && !is_array($customDataSubType)) {
-      $customDataSubType = explode(CRM_Core_DAO::VALUE_SEPARATOR,
-        trim($customDataSubType, CRM_Core_DAO::VALUE_SEPARATOR)
-      );
-    }
+    $customDataSubType = CRM_Utils_Array::explodePadded($customDataSubType);
 
     if (is_array($customDataType)) {
       $cacheKey = implode('_', $customDataType);
@@ -643,6 +641,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         'import' => 1,
         'custom_field_id' => $id,
         'options_per_line' => CRM_Utils_Array::value('options_per_line', $values),
+        'text_length' => CRM_Utils_Array::value('text_length', $values, 255),
         'data_type' => CRM_Utils_Array::value('data_type', $values),
         'html_type' => CRM_Utils_Array::value('html_type', $values),
         'is_search_range' => CRM_Utils_Array::value('is_search_range', $values),
@@ -681,8 +680,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         );
       }
     }
-    return $all ? array(
-      NULL, NULL) : NULL;
+    return $all ? array(NULL, NULL) : NULL;
   }
 
   /**
@@ -725,7 +723,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
    * @param CRM_Core_Form $qf form object (reference)
    * @param string $elementName name of the custom field
    * @param $fieldId
-   * @param boolean $inactiveNeeded
+   * @param boolean $inactiveNeeded -deprecated
    * @param bool $useRequired
    * @param boolean $search true if used for search else false
    * @param string $label label for custom field
@@ -743,6 +741,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     $label          = NULL
   ) {
     $field = self::getFieldObject($fieldId);
+    $widget = $field->html_type;
 
     // Custom field HTML should indicate group+field name
     $groupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id);
@@ -751,29 +750,40 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     $field->attributes .= $dataCrmCustomAttr;
 
     // Fixed for Issue CRM-2183
-    if ($field->html_type == 'TextArea' && $search) {
-      $field->html_type = 'Text';
+    if ($widget == 'TextArea' && $search) {
+      $widget = 'Text';
     }
 
     $placeholder = $search ? ts('- any -') : ($useRequired ? ts('- select -') : ts('- none -'));
 
     // FIXME: Why are select state/country separate widget types?
-    if (in_array($field->html_type, array('Select', 'Multi-Select', 'Select State/Province', 'Multi-Select State/Province', 'Select Country', 'Multi-Select Country'))) {
+    $isSelect = (in_array($widget, array('Select', 'Multi-Select', 'Select State/Province', 'Multi-Select State/Province', 'Select Country', 'Multi-Select Country', 'AdvMulti-Select', 'CheckBox', 'Radio')));
+
+    if ($isSelect) {
+      $options = CRM_Utils_Array::value('values', civicrm_api3('contact', 'getoptions', array('field' => "custom_$fieldId", 'context' => $search ? 'search' : 'create'), array()));
+
+      // Consolidate widget types to simplify the below switch statement
+      if ($search || ($widget !== 'AdvMulti-Select' && strpos($widget, 'Select') !== FALSE)) {
+        $widget = 'Select';
+      }
       $selectAttributes = array(
         'data-crm-custom' => $dataCrmCustomVal,
         'class' => 'crm-select2',
       );
-      if (strpos($field->html_type, 'Multi') === 0) {
+      // Search field is always multi-select
+      if ($search || strpos($field->html_type, 'Multi') !== FALSE) {
+        $selectAttributes['class'] .= ' huge';
         $selectAttributes['multiple'] = 'multiple';
+        $selectAttributes['placeholder'] = $placeholder;
+      }
+      // Add data for popup link. Normally this is handled by CRM_Core_Form->addSelect
+      if ($field->option_group_id && !$search && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM')) {
+        $selectAttributes += array(
+          'data-api-entity' => 'contact', // FIXME: This works because the getoptions api isn't picky about custom fields, but it's WRONG
+          'data-api-field' => 'custom_' . $field->id,
+          'data-option-edit-path' => 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $field->option_group_id),
+        );
       }
-    }
-    // Add data so popup link. Normally this is handled by CRM_Core_Form->addSelect
-    if ($field->option_group_id && !$search && in_array($field->html_type, array('Select', 'Multi-Select')) && CRM_Core_Permission::check('administer CiviCRM')) {
-      $selectAttributes += array(
-        'data-api-entity' => 'contact', // FIXME: This works because the getoptions api isn't picky about custom fields, but it's WRONG
-        'data-api-field' => 'custom_' . $field->id,
-        'data-option-edit-path' => 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $field->option_group_id),
-      );
     }
 
     if (!isset($label)) {
@@ -784,14 +794,15 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
      * at some point in time we might want to split the below into small functions
      **/
 
-    switch ($field->html_type) {
+    switch ($widget) {
       case 'Text':
+      case 'Link':
         if ($field->is_search_range && $search) {
           $qf->add('text', $elementName . '_from', $label . ' ' . ts('From'), $field->attributes);
           $qf->add('text', $elementName . '_to', ts('To'), $field->attributes);
         }
         else {
-          $element = &$qf->add(strtolower($field->html_type), $elementName, $label,
+          $element = &$qf->add('text', $elementName, $label,
             $field->attributes,
             $useRequired && !$search
           );
@@ -815,7 +826,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         if ($field->text_length) {
           $attributes .= ' maxlength=' . $field->text_length;
         }
-        $element = &$qf->add(strtolower($field->html_type),
+        $element = &$qf->add('textarea',
           $elementName,
           $label,
           $attributes,
@@ -860,20 +871,10 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
 
       case 'Radio':
         $choice = array();
-        if ($field->data_type != 'Boolean') {
-          $customOption = CRM_Core_BAO_CustomOption::valuesByID($field->id,
-            $field->option_group_id
-          );
-          foreach ($customOption as $v => $l) {
-            $choice[] = $qf->createElement('radio', NULL, '', $l, (string)$v, $field->attributes);
-          }
-          $group = $qf->addGroup($choice, $elementName, $label);
-        }
-        else {
-          $choice[] = $qf->createElement('radio', NULL, '', ts('Yes'), '1', $field->attributes);
-          $choice[] = $qf->createElement('radio', NULL, '', ts('No'), '0', $field->attributes);
-          $group = $qf->addGroup($choice, $elementName, $label);
+        foreach ($options as $v => $l) {
+          $choice[] = $qf->createElement('radio', NULL, '', $l, (string)$v, $field->attributes);
         }
+        $group = $qf->addGroup($choice, $elementName, $label);
         if ($useRequired && !$search) {
           $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
         }
@@ -882,34 +883,30 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         }
         break;
 
+      // For all select elements
       case 'Select':
-        $selectOption = CRM_Core_BAO_CustomOption::valuesByID($field->id,
-          $field->option_group_id
-        );
-
-        $qf->add('select', $elementName, $label,
-          array('' => $placeholder) + $selectOption,
-          $useRequired && !$search,
-          $selectAttributes
-        );
-        break;
+        if (empty($selectAttributes['multiple'])) {
+          $options = array('' => $placeholder) + $options;
+        }
+        $qf->add('select', $elementName, $label, $options, $useRequired && !$search, $selectAttributes);
 
-      //added for select multiple
+        // Add and/or option for fields that store multiple values
+        if ($search && self::isSerialized($field)) {
 
-      case 'AdvMulti-Select':
-        $selectOption = CRM_Core_BAO_CustomOption::valuesByID($field->id,
-          $field->option_group_id
-        );
-        if ($search &&
-          count($selectOption) > 1
-        ) {
-          $selectOption['CiviCRM_OP_OR'] = ts('Select to match ANY; unselect to match ALL');
+          $operators = array(
+            $qf->createElement('radio', NULL, '', ts('Any'), 'or', array('title' => ts('Results may contain any of the selected options'))),
+            $qf->createElement('radio', NULL, '', ts('All'), 'and', array('title' => ts('Results must have all of the selected options'))),
+          );
+          $qf->addGroup($operators, $elementName . '_operator');
+          $qf->setDefaults(array($elementName . '_operator' => 'or'));
         }
+        break;
 
+      case 'AdvMulti-Select':
         $include =& $qf->addElement(
           'advmultiselect',
           $elementName,
-          $label, $selectOption,
+          $label, $options,
           array(
             'size' => 5,
             'style' => '',
@@ -926,35 +923,11 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         }
         break;
 
-      case 'Multi-Select':
-        $selectOption = CRM_Core_BAO_CustomOption::valuesByID($field->id,
-          $field->option_group_id
-        );
-        if ($search &&
-          count($selectOption) > 1
-        ) {
-          $selectOption['CiviCRM_OP_OR'] = ts('Select to match ANY; unselect to match ALL');
-        }
-        $qf->addElement('select', $elementName, $label, $selectOption, $selectAttributes);
-
-        if ($useRequired && !$search) {
-          $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
-        }
-        break;
-
       case 'CheckBox':
-        $customOption = CRM_Core_BAO_CustomOption::valuesByID($field->id,
-          $field->option_group_id
-        );
         $check = array();
-        foreach ($customOption as $v => $l) {
+        foreach ($options as $v => $l) {
           $check[] = &$qf->addElement('advcheckbox', $v, NULL, $l, array('data-crm-custom' => $dataCrmCustomVal));
         }
-        if ($search &&
-          count($check) > 1
-        ) {
-          $check[] = &$qf->addElement('advcheckbox', 'CiviCRM_OP_OR', NULL, ts('Check to match ANY; uncheck to match ALL'), array('data-crm-custom' => $dataCrmCustomVal));
-        }
         $qf->addGroup($check, $elementName, $label);
         if ($useRequired && !$search) {
           $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
@@ -976,45 +949,6 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         $qf->addUploadElement($elementName);
         break;
 
-      case 'Select State/Province':
-        //Add State
-        $stateOption = array('' => $placeholder) + CRM_Core_PseudoConstant::stateProvince();
-        $qf->add('select', $elementName, $label, $stateOption,
-          $useRequired && !$search,
-          $selectAttributes
-        );
-        $qf->_stateCountryMap['state_province'][] = $elementName;
-        break;
-
-      case 'Multi-Select State/Province':
-        //Add Multi-select State/Province
-        $stateOption = CRM_Core_PseudoConstant::stateProvince();
-
-        $qf->addElement('select', $elementName, $label, $stateOption, $selectAttributes);
-        if ($useRequired && !$search) {
-          $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
-        }
-        break;
-
-      case 'Select Country':
-        //Add Country
-        $countryOption = array('' => $placeholder) + CRM_Core_PseudoConstant::country();
-        $qf->add('select', $elementName, $label, $countryOption,
-          $useRequired && !$search,
-          $selectAttributes
-        );
-        $qf->_stateCountryMap['country'][] = $elementName;
-        break;
-
-      case 'Multi-Select Country':
-        //Add Country
-        $countryOption = CRM_Core_PseudoConstant::country();
-        $qf->addElement('select', $elementName, $label, $countryOption, $selectAttributes);
-        if ($useRequired && !$search) {
-          $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
-        }
-        break;
-
       case 'RichTextEditor':
         $attributes = array('rows' => $field->note_rows, 'cols' => $field->note_columns, 'data-crm-custom' => $dataCrmCustomVal);
         if ($field->text_length) {
@@ -1055,6 +989,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
           $attributes += array(
             'entity' => 'option_value',
             'placeholder' => $placeholder,
+            'multiple' => $search,
             'api' => array(
               'params' => array('option_group_id' => $field->option_group_id),
             ),
@@ -1073,7 +1008,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
           $qf->addRule($elementName . '_from', ts('%1 From must be an integer (whole number).', array(1 => $label)), 'integer');
           $qf->addRule($elementName . '_to', ts('%1 To must be an integer (whole number).', array(1 => $label)), 'integer');
         }
-        else {
+        elseif ($widget == 'Text') {
           $qf->addRule($elementName, ts('%1 must be an integer (whole number).', array(1 => $label)), 'integer');
         }
         break;
@@ -1083,7 +1018,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
           $qf->addRule($elementName . '_from', ts('%1 From must be a number (with or without decimal point).', array(1 => $label)), 'numeric');
           $qf->addRule($elementName . '_to', ts('%1 To must be a number (with or without decimal point).', array(1 => $label)), 'numeric');
         }
-        else {
+        elseif ($widget == 'Text') {
           $qf->addRule($elementName, ts('%1 must be a number (with or without decimal point).', array(1 => $label)), 'numeric');
         }
         break;
@@ -1093,23 +1028,15 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
           $qf->addRule($elementName . '_from', ts('%1 From must in proper money format. (decimal point/comma/space is allowed).', array(1 => $label)), 'money');
           $qf->addRule($elementName . '_to', ts('%1 To must in proper money format. (decimal point/comma/space is allowed).', array(1 => $label)), 'money');
         }
-        else {
+        elseif ($widget == 'Text') {
           $qf->addRule($elementName, ts('%1 must be in proper money format. (decimal point/comma/space is allowed).', array(1 => $label)), 'money');
         }
         break;
 
       case 'Link':
-        $qf->add(
-          'text',
-          $elementName,
-          $label,
-          array(
-            'onfocus' => "if (!this.value) {  this.value='http://';} else return false",
-            'onblur' => "if ( this.value == 'http://') {  this.value='';} else return false",
-            'data-crm-custom' => $dataCrmCustomVal,
-          ),
-          $useRequired && !$search
-        );
+        $element->setAttribute('onfocus', "if (!this.value) {this.value='http://';}");
+        $element->setAttribute('onblur', "if (this.value == 'http://') {this.value='';}");
+        $element->setAttribute('class', "url");
         $qf->addRule($elementName, ts('Enter a valid Website.'), 'wikiURL');
         break;
     }
@@ -1269,10 +1196,6 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         $v = array();
         $p = array();
         foreach ($checkedData as $key => $val) {
-          if ($key === 'CiviCRM_OP_OR') {
-            continue;
-          }
-
           if ($html_type == 'CheckBox') {
             if ($val) {
               $p[] = $key;
@@ -1524,7 +1447,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
    *
    * @return array
    */
-  static function getFileURL($contactID, $cfID, $fileID = NULL, $absolute = FALSE) {
+  static function getFileURL($contactID, $cfID, $fileID = NULL, $absolute = FALSE, $multiRecordWhereClause = NULL) {
     if ($contactID) {
       if (!$fileID) {
         $params = array('id' => $cfID);
@@ -1539,7 +1462,12 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
         );
 
         //query to fetch id from civicrm_file
-        $query = "SELECT {$columnName} FROM {$tableName} where entity_id = {$contactID}";
+        if ($multiRecordWhereClause) {
+          $query = "SELECT {$columnName} FROM {$tableName} where entity_id = {$contactID} AND {$multiRecordWhereClause}";
+        }
+        else {
+          $query = "SELECT {$columnName} FROM {$tableName} where entity_id = {$contactID}";
+        }
         $fileID = CRM_Core_DAO::singleValueQuery($query);
       }
 
@@ -1603,6 +1531,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
    * @param int     $entityId            entity id (contribution, membership...)
    * @param boolean $inline              consider inline custom groups only
    * @param boolean $checkPermission     if false, do not include permissioning clause
+   * @param boolean $includeViewOnly     if true, fields marked 'View Only' are included. Required for APIv3.
    *
    * @return array $customFormatted formatted custom field array
    * @static
@@ -1611,7 +1540,8 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     $customFieldExtend, $customValueId = NULL,
     $entityId = NULL,
     $inline = FALSE,
-    $checkPermission = TRUE
+    $checkPermission = TRUE,
+    $includeViewOnly = false
   ) {
     //get the custom fields for the entity
     //subtype and basic type
@@ -1644,7 +1574,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     }
 
     // return if field is a 'code' field
-    if (!empty($customFields[$customFieldId]['is_view'])) {
+    if (!$includeViewOnly && !empty($customFields[$customFieldId]['is_view'])) {
       return;
     }
 
@@ -2551,5 +2481,17 @@ WHERE cf.id = %1 AND cg.is_multiple = 1";
 
     return $isMultipleWithGid;
   }
+
+  /**
+   * Does this field store a serialized string?
+   * @param CRM_Core_DAO_CustomField|array $field
+   * @return bool
+   */
+  static function isSerialized($field) {
+    // Fields retrieved via api are an array, or from the dao are an object. We'll accept either.
+    $field = (array) $field;
+    // FIXME: Currently the only way to know if data is serialized is by looking at the html_type. It would be cleaner to decouple this.
+    return ($field['html_type'] == 'CheckBox' || strpos($field['html_type'], 'Multi') !== FALSE);
+  }
 }