CRM-14842 decode utf prior to checking strlen
[civicrm-core.git] / api / v3 / utils.php
index f810fcbf42505af0f47c9e5b031f98fe701b0ada..849f70677d1815148391e268c2fcd1aa3470f058 100644 (file)
@@ -48,7 +48,7 @@ function _civicrm_api3_initialize() {
 /**
  * Wrapper Function for civicrm_verify_mandatory to make it simple to pass either / or fields for checking
  *
- * @param array $params array of fields to check
+ * @param array $params array of fields to checkl
  * @param array $daoName string DAO to check for required fields (create functions only)
  * @param array $keyoptions
  *
@@ -253,7 +253,13 @@ function civicrm_api3_create_success($values = 1, $params = array(
   else {
     $result['values'] = $values;
   }
-
+  if(!empty($params['options']['metadata'])) {
+    // we've made metadata an array but only supporting 'fields' atm
+    if(in_array('fields', $params['options']['metadata'])) {
+      $fields = civicrm_api3($entity, 'getfields', array('action' => $action));
+      $result['metadata']['fields'] = $fields['values'];
+    }
+  }
   return array_merge($result, $extraReturnValues);
 }
 
@@ -384,6 +390,8 @@ function _civicrm_api3_store_values(&$fields, &$params, &$values) {
  *  others that use the query object. Note that this function passes permission information in.
  *  The others don't
  *
+ * * Ideally this would be merged with _civicrm_get_query_object but we need to resolve differences in what the
+ * 2 variants call
  * @param $entity
  * @param array $params as passed into api get or getcount function
  * @param array $additional_options
@@ -455,7 +463,6 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti
 
   }
   $skipPermissions = CRM_Utils_Array::value('check_permissions', $params)? 0 :1;
-
   list($entities, $options) = CRM_Contact_BAO_Query::apiQuery(
     $newParams,
     $returnProperties,
@@ -474,6 +481,45 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti
   return $entities;
 }
 
+/**
+ * get dao query object based on input params
+ * Ideally this would be merged with _civicrm_get_using_query_object but we need to resolve differences in what the
+ * 2 variants call
+ *
+ * @param array $params
+ * @param string $mode
+ * @param string $entity
+ * @return CRM_Core_DAO query object
+ */
+function _civicrm_api3_get_query_object($params, $mode, $entity) {
+  $options          = _civicrm_api3_get_options_from_params($params, TRUE, $entity, 'get');
+  $sort             = CRM_Utils_Array::value('sort', $options, NULL);
+  $offset           = CRM_Utils_Array::value('offset', $options);
+  $rowCount         = CRM_Utils_Array::value('limit', $options);
+  $inputParams      = CRM_Utils_Array::value('input_params', $options, array());
+  $returnProperties = CRM_Utils_Array::value('return', $options, NULL);
+  if (empty($returnProperties)) {
+    $returnProperties = CRM_Contribute_BAO_Query::defaultReturnProperties($mode);
+  }
+
+  $newParams = CRM_Contact_BAO_Query::convertFormValues($inputParams);
+  $query = new CRM_Contact_BAO_Query($newParams, $returnProperties, NULL,
+    FALSE, FALSE, $mode
+  );
+  list($select, $from, $where, $having) = $query->query();
+
+  $sql = "$select $from $where $having";
+
+  if (!empty($sort)) {
+    $sql .= " ORDER BY $sort ";
+  }
+  if(!empty($rowCount)) {
+    $sql .= " LIMIT $offset, $rowCount ";
+  }
+  $dao = CRM_Core_DAO::executeQuery($sql);
+  return array($dao, $query);
+}
+
 /**
  * Function transfers the filters being passed into the DAO onto the params object
  */
@@ -559,6 +605,7 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
       $dao->selectAdd($allfields[$uniqueVal]);
     }
   }
+  $dao->setApiFilter($params);
 }
 
 /**
@@ -699,7 +746,9 @@ function _civicrm_api3_apply_options_to_dao(&$params, &$dao, $entity) {
 
   $options = _civicrm_api3_get_options_from_params($params,FALSE,$entity);
   if(!$options['is_count']) {
-    $dao->limit((int)$options['offset'], (int)$options['limit']);
+    if(!empty($options['limit'])) {
+      $dao->limit((int)$options['offset'], (int)$options['limit']);
+    }
     if (!empty($options['sort'])) {
       $dao->orderBy($options['sort']);
     }
@@ -754,12 +803,15 @@ function _civicrm_api3_get_unique_name_array(&$bao) {
  * @static void
  * @access public
  */
-function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE, $entity = "") {
+function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE, $entity = "", $autoFind = TRUE) {
   $result = array();
   if(isset($params['options']) && CRM_Utils_Array::value('is_count', $params['options'])) {
     return $dao->count();
   }
-  if (empty($dao) || !$dao->find()) {
+  if (empty($dao)) {
+    return array();
+  }
+  if ($autoFind && !$dao->find()) {
     return array();
   }
 
@@ -835,9 +887,25 @@ function _civicrm_api3_object_to_array_unique_fields(&$dao, &$values) {
  */
 function _civicrm_api3_custom_format_params($params, &$values, $extends, $entityId = NULL) {
   $values['custom'] = array();
+  $checkCheckBoxField = FALSE;
+  $entity = $extends;
+  if(in_array($extends, array('Household', 'Individual', 'Organization'))) {
+    $entity = 'Contact';
+  }
+
+  $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => 'create'));
+  if(!$fields['is_error']) {
+    // not sure if fields could be error - maybe change to using civicrm_api3 wrapper later - this is conservative
+    $fields = $fields['values'];
+    $checkCheckBoxField = TRUE;
+  }
+
   foreach ($params as $key => $value) {
     list($customFieldID, $customValueID) = CRM_Core_BAO_CustomField::getKeyID($key, TRUE);
     if ($customFieldID && (!IS_NULL($value))) {
+      if ($checkCheckBoxField && !empty($fields['custom_' . $customFieldID]) && $fields['custom_' . $customFieldID]['html_type'] == 'CheckBox') {
+        formatCheckBoxField($value, 'custom_' . $customFieldID, $entity);
+      }
       CRM_Core_BAO_CustomField::formatCustomField($customFieldID, $values['custom'],
         $value, $extends, $customValueID, $entityId, FALSE, FALSE
       );
@@ -845,6 +913,101 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity
   }
 }
 
+/**
+ * we can't rely on downstream to add separators to checkboxes so we'll check here. We should look at pushing to BAO function
+ * and / or validate function but this is a safe place for now as it has massive test coverage & we can keep the change very specific
+ * note that this is specifically tested in the GRANT api test case so later refactoring should use that as a checking point
+ *
+ * We will only alter the value if we are sure that changing it will make it correct - if it appears wrong but does not appear to have a clear fix we
+ * don't touch - lots of very cautious code in here
+ *
+ * The resulting array should look like
+ * array(
+ *  'key' => 1,
+ *  'key1' => 1,
+ * );
+ *
+ * OR one or more keys wrapped in a CRM_Core_DAO::VALUE_SEPARATOR - either it accepted by the receiving function
+ *
+ * @todo - we are probably skipping handling disabled options as presumably getoptions is not giving us them. This should be non-regressive but might
+ * be fixed in future
+ *
+ * @param $checkboxFieldValue
+ * @param $customFieldLabel
+ * @param $entity
+ *
+ */
+function formatCheckBoxField(&$checkboxFieldValue, $customFieldLabel, $entity) {
+
+  if (is_string($checkboxFieldValue) && stristr($checkboxFieldValue, CRM_Core_DAO::VALUE_SEPARATOR)) {
+    // we can assume it's pre-formatted
+    return;
+  }
+  $options = civicrm_api($entity, 'getoptions', array('field' => $customFieldLabel, 'version' => 3));
+  if (!empty($options['is_error'])) {
+    //the check is precautionary - can probably be removed later
+    return;
+  }
+
+  $options = $options['values'];
+  $validValue = TRUE;
+  if (is_array($checkboxFieldValue)) {
+    foreach ($checkboxFieldValue as $key => $value) {
+      if (!array_key_exists($key, $options)) {
+        $validValue = FALSE;
+      }
+    }
+    if ($validValue) {
+      // we have been passed an array that is already in the 'odd' custom field format
+      return;
+    }
+  }
+
+  // so we either have an array that is not keyed by the value or we have a string that doesn't hold separators
+  // if the array only has one item we'll treat it like any other string
+  if (is_array($checkboxFieldValue) && count($checkboxFieldValue) == 1) {
+    $possibleValue = reset($checkboxFieldValue);
+  }
+  if (is_string($checkboxFieldValue)) {
+    $possibleValue = $checkboxFieldValue;
+  }
+  if (isset($possibleValue) && array_key_exists($possibleValue, $options)) {
+    $checkboxFieldValue = CRM_Core_DAO::VALUE_SEPARATOR . $possibleValue . CRM_Core_DAO::VALUE_SEPARATOR;
+    return;
+  }
+  elseif (is_array($checkboxFieldValue)) {
+    // so this time around we are considering the values in the array
+    $possibleValues = $checkboxFieldValue;
+    $formatValue = TRUE;
+  }
+  elseif (stristr($checkboxFieldValue, ',')) {
+    $formatValue = TRUE;
+    //lets see if we should separate it - we do this near the end so we
+    // ensure we have already checked that the comma is not part of a legitimate match
+    // and of course, we don't make any changes if we don't now have matches
+    $possibleValues = explode(',', $checkboxFieldValue);
+  }
+  else {
+    // run out of ideas as to what the format might be - if it's a string it doesn't match with or without the ','
+    return;
+  }
+
+  foreach ($possibleValues as $index => $possibleValue) {
+    if (array_key_exists($possibleValue, $options)) {
+      // do nothing - we will leave formatValue set to true unless another value is not found (which would cause us to ignore the whole value set)
+    }
+    elseif (array_key_exists(trim($possibleValue), $options)) {
+      $possibleValues[$index] = trim($possibleValue);
+    }
+    else {
+      $formatValue = FALSE;
+    }
+  }
+  if ($formatValue) {
+    $checkboxFieldValue = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $possibleValues) . CRM_Core_DAO::VALUE_SEPARATOR;
+  }
+}
+
 /**
  * @deprecated
  * This function ensures that we have the right input parameters
@@ -965,9 +1128,9 @@ function _civicrm_api3_api_check_permission($entity, $action, &$params, $throw =
  */
 function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $entity = "") {
   $bao = new $bao_name();
-  _civicrm_api3_dao_set_filter($bao, $params, TRUE,$entity);
+  _civicrm_api3_dao_set_filter($bao, $params, TRUE, $entity);
   if ($returnAsSuccess) {
-      return civicrm_api3_create_success(_civicrm_api3_dao_to_array($bao, $params, FALSE, $entity), $params, $entity);
+      return civicrm_api3_create_success(_civicrm_api3_dao_to_array($bao, $params, FALSE, $entity), $params, $entity, 'get');
   }
   else {
     return _civicrm_api3_dao_to_array($bao, $params, FALSE, $entity);
@@ -1209,7 +1372,8 @@ function _civicrm_api3_validate_date(&$params, &$fieldName, &$fieldInfo) {
     if (strtotime($params[$fieldInfo['name']]) === FALSE) {
       throw new Exception($fieldInfo['name'] . " is not a valid date: " . $params[$fieldInfo['name']]);
     }
-    $params[$fieldInfo['name']] = CRM_Utils_Date::processDate($params[$fieldInfo['name']]);
+    $format = ($fieldInfo['type'] == CRM_Utils_Type::T_DATE) ? 'Ymd000000' : 'YmdHis';
+    $params[$fieldInfo['name']] = CRM_Utils_Date::processDate($params[$fieldInfo['name']], NULL, FALSE, $format);
   }
   if ((CRM_Utils_Array::value('name', $fieldInfo) != $fieldName) && CRM_Utils_Array::value($fieldName, $params)) {
     //If the unique field name differs from the db name & is set handle it here
@@ -1583,12 +1747,9 @@ function _civicrm_api3_validate_integer(&$params, &$fieldName, &$fieldInfo, $ent
 function  _civicrm_api3_resolve_contactID($contactIdExpr) {
   //if value = 'user_contact_id' replace value with logged in user id
   if ($contactIdExpr == "user_contact_id") {
-    $session = &CRM_Core_Session::singleton();
-    if (!is_numeric($session->get('userID'))) {
-      return NULL;
-    }
-    return $session->get('userID');
-  } elseif (preg_match('/^@user:(.*)$/', $contactIdExpr, $matches)) {
+    return CRM_Core_Session::getLoggedInContactID();
+  }
+  elseif (preg_match('/^@user:(.*)$/', $contactIdExpr, $matches)) {
     $config = CRM_Core_Config::singleton();
 
     $ufID = $config->userSystem->getUfId($matches[1]);
@@ -1656,8 +1817,8 @@ function _civicrm_api3_validate_string(&$params, &$fieldName, &$fieldInfo, $enti
       _civicrm_api3_api_match_pseudoconstant($params, $entity, $fieldName, $fieldInfo);
     }
     // Check our field length
-    elseif (is_string($value) && !empty($fieldInfo['maxlength']) && strlen($value) > $fieldInfo['maxlength']) {
-      throw new API_Exception("Value for $fieldName is " . strlen($value) . " characters  - This field has a maxlength of {$fieldInfo['maxlength']} characters.",
+    elseif (is_string($value) && !empty($fieldInfo['maxlength']) && strlen(utf8_decode($value)) > $fieldInfo['maxlength']) {
+      throw new API_Exception("Value for $fieldName is " . strlen(utf8_decode($value)) . " characters  - This field has a maxlength of {$fieldInfo['maxlength']} characters.",
         2100, array('field' => $fieldName)
       );
     }