INFRA-132 - phpcbf Drupal.WhiteSpace.ScopeIndent.IncorrectExact
[civicrm-core.git] / api / v3 / utils.php
index 0f5c37248d5800c6908bb06b238478ea4a900c2d..c2ada33f336073e367ef3603ec0bc87a0fb7f28a 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /*
   +--------------------------------------------------------------------+
-  | CiviCRM version 4.4                                                |
+  | CiviCRM version 4.6                                                |
   +--------------------------------------------------------------------+
-  | Copyright CiviCRM LLC (c) 2004-2013                                |
+  | Copyright CiviCRM LLC (c) 2004-2014                                |
   +--------------------------------------------------------------------+
   | This file is a part of CiviCRM.                                    |
   |                                                                    |
@@ -23,7 +23,7 @@
   | GNU Affero General Public License or the licensing of CiviCRM,     |
   | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
   +--------------------------------------------------------------------+
-*/
+ */
 
 /**
  * File for CiviCRM APIv3 utilitity functions
@@ -31,7 +31,7 @@
  * @package CiviCRM_APIv3
  * @subpackage API_utils
  *
- * @copyright CiviCRM LLC (c) 2004-2013
+ * @copyright CiviCRM LLC (c) 2004-2014
  * @version $Id: utils.php 30879 2010-11-22 15:45:55Z shot $
  *
  */
@@ -48,16 +48,14 @@ 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 checkl
- * @param array $daoName string DAO to check for required fields (create functions only)
+ * @param array $params
+ *   Array of fields to check.
+ * @param array $daoName
+ *   String DAO to check for required fields (create functions only).
  * @param array $keyoptions
- *
- * @internal param array $keys list of required fields options. One of the options is required
- * @return null or throws error if there the required fields not present
- * @
+ *   List of required fields options. One of the options is required.
  */
-function civicrm_api3_verify_one_mandatory($params, $daoName = NULL, $keyoptions = array(
-  )) {
+function civicrm_api3_verify_one_mandatory($params, $daoName = NULL, $keyoptions = array()) {
   $keys = array(array());
   foreach ($keyoptions as $key) {
     $keys[0][] = $key;
@@ -66,20 +64,17 @@ function civicrm_api3_verify_one_mandatory($params, $daoName = NULL, $keyoptions
 }
 
 /**
- * Function to check mandatory fields are included
+ * check mandatory fields are included
  *
- * @param array $params array of fields to check
- * @param array $daoName string DAO to check for required fields (create functions only)
- * @param array $keys list of required fields. A value can be an array denoting that either this or that is required.
+ * @param array $params
+ *   Array of fields to check.
+ * @param array $daoName
+ *   String DAO to check for required fields (create functions only).
+ * @param array $keys
+ *   List of required fields. A value can be an array denoting that either this or that is required.
  * @param bool $verifyDAO
- *
- * @throws API_Exception
- * @return null or throws error if there the required fields not present
- *
- * @todo see notes on _civicrm_api3_check_required_fields regarding removing $daoName param
  */
-function civicrm_api3_verify_mandatory($params, $daoName = NULL, $keys = array(
-  ), $verifyDAO = TRUE) {
+function civicrm_api3_verify_mandatory($params, $daoName = NULL, $keys = array(), $verifyDAO = TRUE) {
 
   $unmatched = array();
   if ($daoName != NULL && $verifyDAO && empty($params['id'])) {
@@ -124,46 +119,26 @@ function civicrm_api3_verify_mandatory($params, $daoName = NULL, $keys = array(
     }
   }
   if (!empty($unmatched)) {
-    throw new API_Exception("Mandatory key(s) missing from params array: " . implode(", ", $unmatched),"mandatory_missing",array("fields"=>$unmatched));
+    throw new API_Exception("Mandatory key(s) missing from params array: " . implode(", ", $unmatched), "mandatory_missing", array("fields" => $unmatched));
   }
 }
 
 /**
  *
- * @param <type> $data
+ * @param $msg
  * @param array $data
- * @param object $dao DAO / BAO object to be freed here
- *
- * @throws API_Exception
- * @return array <type>
+ * @return array
  */
-function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) {
-  //fix me - $dao should be param 4 & 3 should be $apiRequest
-  if (is_object($dao)) {
-    $dao->free();
-  }
-
-  if (is_array($dao)) {
-    if ($msg == 'DB Error: constraint violation' || substr($msg, 0,9)  == 'DB Error:' || $msg == 'DB Error: already exists') {
-      try {
-        $fields = _civicrm_api3_api_getfields($dao);
-        _civicrm_api3_validate_fields($dao['entity'], $dao['action'], $dao['params'], $fields, TRUE);
-      }
-      catch(Exception $e) {
-        $msg = $e->getMessage();
-      }
-    }
-  }
+function civicrm_api3_create_error($msg, $data = array()) {
   $data['is_error'] = 1;
   $data['error_message'] = $msg;
-  // we will show sql to privelledged user only (not sure of a specific
-  // security hole here but seems sensible - perhaps should apply to the trace as well?
-  if(isset($data['sql']) && CRM_Core_Permission::check('Administer CiviCRM')) {
-    $data['debug_information'] = $data['sql'];
+  // we will show sql to privileged user only (not sure of a specific
+  // security hole here but seems sensible - perhaps should apply to the trace as well?)
+  if (isset($data['sql']) && CRM_Core_Permission::check('Administer CiviCRM')) {
+    $data['debug_information'] = $data['sql']; // Isn't this redundant?
   }
-  if (is_array($dao) && isset($dao['params']) && is_array($dao['params']) && CRM_Utils_Array::value('api.has_parent', $dao['params'])) {
-    $errorCode = empty($data['error_code']) ? 'chained_api_failed' : $data['error_code'];
-    throw new API_Exception('Error in call to ' . $dao['entity'] . '_' . $dao['action'] . ' : ' . $msg, $errorCode, $data);
+  else {
+    unset($data['sql']);
   }
   return $data;
 }
@@ -171,31 +146,35 @@ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) {
 /**
  * Format array in result output styple
  *
- * @param array $values values generated by API operation (the result)
- * @param array $params parameters passed into API call
- * @param string $entity the entity being acted on
- * @param string $action the action passed to the API
- * @param object $dao DAO object to be freed here
- * @param array $extraReturnValues additional values to be added to top level of result array(
+ * @param array|int $values values generated by API operation (the result)
+ * @param array $params
+ *   Parameters passed into API call.
+ * @param string $entity
+ *   The entity being acted on.
+ * @param string $action
+ *   The action passed to the API.
+ * @param object $dao
+ *   DAO object to be freed here.
+ * @param array $extraReturnValues
+ *   Additional values to be added to top level of result array(.
  *   - this param is currently used for legacy behaviour support
  *
- * @return array $result
+ * @return array
  */
-function civicrm_api3_create_success($values = 1, $params = array(
-  ), $entity = NULL, $action = NULL, &$dao = NULL, $extraReturnValues = array()) {
+function civicrm_api3_create_success($values = 1, $params = array(), $entity = NULL, $action = NULL, &$dao = NULL, $extraReturnValues = array()) {
   $result = array();
   $result['is_error'] = 0;
   //lets set the ['id'] field if it's not set & we know what the entity is
-  if (is_array($values) && !empty($entity)) {
+  if (is_array($values) && !empty($entity) && $action != 'getfields') {
     foreach ($values as $key => $item) {
       if (empty($item['id']) && !empty($item[$entity . "_id"])) {
         $values[$key]['id'] = $item[$entity . "_id"];
       }
-      if(!empty($item['financial_type_id'])){
+      if (!empty($item['financial_type_id'])) {
         //4.3 legacy handling
         $values[$key]['contribution_type_id'] = $item['financial_type_id'];
       }
-      if(!empty($item['next_sched_contribution_date'])){
+      if (!empty($item['next_sched_contribution_date'])) {
         // 4.4 legacy handling
         $values[$key]['next_sched_contribution'] = $item['next_sched_contribution_date'];
       }
@@ -218,7 +197,7 @@ function civicrm_api3_create_success($values = 1, $params = array(
       $allFields = array_keys($apiFields['values']);
     }
     $paramFields = array_keys($params);
-    $undefined = array_diff($paramFields, $allFields, array_keys($_COOKIE), array('action', 'entity', 'debug', 'version', 'check_permissions', 'IDS_request_uri', 'IDS_user_agent', 'return', 'sequential', 'rowCount', 'option_offset', 'option_limit', 'custom', 'option_sort', 'options'));
+    $undefined = array_diff($paramFields, $allFields, array_keys($_COOKIE), array('action', 'entity', 'debug', 'version', 'check_permissions', 'IDS_request_uri', 'IDS_user_agent', 'return', 'sequential', 'rowCount', 'option_offset', 'option_limit', 'custom', 'option_sort', 'options', 'prettyprint'));
     if ($undefined) {
       $result['undefined_fields'] = array_merge($undefined);
     }
@@ -253,18 +232,36 @@ function civicrm_api3_create_success($values = 1, $params = array(
   else {
     $result['values'] = $values;
   }
-  if(!empty($params['options']['metadata'])) {
+  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));
+    if (in_array('fields', (array) $params['options']['metadata']) && $action !== 'getfields') {
+      $fields = civicrm_api3($entity, 'getfields', array('action' => substr($action, 0, 3) == 'get' ? 'get' : 'create'));
       $result['metadata']['fields'] = $fields['values'];
     }
   }
+  // Report deprecations
+  $deprecated = _civicrm_api3_deprecation_check($entity, $result);
+  // Always report "update" action as deprecated
+  if (!is_string($deprecated) && ($action == 'getactions' || $action == 'update')) {
+    $deprecated = ((array) $deprecated) + array('update' => 'The "update" action is deprecated. Use "create" with an id instead.');
+  }
+  if ($deprecated) {
+    // Metadata-level deprecations or wholesale entity deprecations
+    if ($entity == 'entity' || $action == 'getactions' || is_string($deprecated)) {
+      $result['deprecated'] = $deprecated;
+    }
+    // Action-specific deprecations
+    elseif (!empty($deprecated[$action])) {
+      $result['deprecated'] = $deprecated[$action];
+    }
+  }
   return array_merge($result, $extraReturnValues);
 }
 
 /**
  * Load the DAO of the entity
+ * @param $entity
+ * @return bool
  */
 function _civicrm_api3_load_DAO($entity) {
   $dao = _civicrm_api3_get_DAO($entity);
@@ -276,10 +273,11 @@ function _civicrm_api3_load_DAO($entity) {
 }
 
 /**
- * Function to return the DAO of the function or Entity
- * @param String $name  either a function of the api (civicrm_{entity}_create or the entity name
- * return the DAO name to manipulate this function
- * eg. "civicrm_api3_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
+ * return the DAO of the function or Entity
+ * @param string $name
+ *   Either a function of the api (civicrm_{entity}_create or the entity name.
+ *   return the DAO name to manipulate this function
+ *   eg. "civicrm_api3_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
  * @return mixed|string
  */
 function _civicrm_api3_get_DAO($name) {
@@ -289,31 +287,72 @@ function _civicrm_api3_get_DAO($name) {
     $name = substr($name, 13, $last - 13);
   }
 
-  if (strtolower($name) == 'individual' || strtolower($name) == 'household' || strtolower($name) == 'organization') {
+  $name = _civicrm_api_get_camel_name($name, 3);
+
+  if ($name == 'Individual' || $name == 'Household' || $name == 'Organization') {
     $name = 'Contact';
   }
 
-  //hack to deal with incorrectly named BAO/DAO - see CRM-10859 -
-  // several of these have been removed but am not confident mailing_recipients is
-  // tests so have not tackled.
-  // correct approach for im is unclear
-  if($name == 'mailing_recipients' || $name == 'MailingRecipients'){
-    return 'CRM_Mailing_BAO_Recipients';
+  // hack to deal with incorrectly named BAO/DAO - see CRM-10859
+
+  // FIXME: DAO should be renamed CRM_Mailing_DAO_MailingEventQueue
+  if ($name == 'MailingEventQueue') {
+    return 'CRM_Mailing_Event_DAO_Queue';
+  }
+  // FIXME: DAO should be renamed CRM_Mailing_DAO_MailingRecipients
+  // but am not confident mailing_recipients is tested so have not tackled.
+  if ($name == 'MailingRecipients') {
+    return 'CRM_Mailing_DAO_Recipients';
+  }
+  // FIXME: DAO should be renamed CRM_Mailing_DAO_MailingComponent
+  if ($name == 'MailingComponent') {
+    return 'CRM_Mailing_DAO_Component';
+  }
+  // FIXME: DAO should be renamed CRM_ACL_DAO_AclRole
+  if ($name == 'AclRole') {
+    return 'CRM_ACL_DAO_EntityRole';
+  }
+  // FIXME: DAO should be renamed CRM_SMS_DAO_SmsProvider
+  // But this would impact SMS extensions so need to coordinate
+  // Probably best approach is to migrate them to use the api and decouple them from core BAOs
+  if ($name == 'SmsProvider') {
+    return 'CRM_SMS_DAO_Provider';
+  }
+  // FIXME: DAO names should follow CamelCase convention
+  if ($name == 'Im' || $name == 'Acl') {
+    $name = strtoupper($name);
+  }
+  $dao = CRM_Core_DAO_AllCoreTables::getFullName($name);
+  if ($dao || !$name) {
+    return $dao;
+  }
+
+  // Really weird apis can declare their own DAO name. Not sure if this is a good idea...
+  if (file_exists("api/v3/$name.php")) {
+    include_once "api/v3/$name.php";
   }
-  if(strtolower($name) == 'im'){
-    return 'CRM_Core_BAO_IM';
+
+  $daoFn = "_civicrm_api3_" . _civicrm_api_get_entity_name_from_camel($name) . "_DAO";
+  if (function_exists($daoFn)) {
+    return $daoFn();
   }
-  return CRM_Core_DAO_AllCoreTables::getFullName(_civicrm_api_get_camel_name($name, 3));
+
+  return NULL;
 }
 
 /**
- * Function to return the DAO of the function or Entity
- * @param String $name is either a function of the api (civicrm_{entity}_create or the entity name
- * return the DAO name to manipulate this function
- * eg. "civicrm_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
+ * return the DAO of the function or Entity
+ * @param string $name
+ *   Is either a function of the api (civicrm_{entity}_create or the entity name.
+ *   return the DAO name to manipulate this function
+ *   eg. "civicrm_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
  * @return mixed
  */
 function _civicrm_api3_get_BAO($name) {
+  // FIXME: DAO should be renamed CRM_Badge_DAO_BadgeLayout
+  if ($name == 'PrintLabel') {
+    return 'CRM_Badge_BAO_Layout';
+  }
   $dao = _civicrm_api3_get_DAO($name);
   if (!$dao) {
     return NULL;
@@ -326,7 +365,7 @@ function _civicrm_api3_get_BAO($name) {
 
 /**
  *  Recursive function to explode value-separated strings into arrays
- *
+ * @param $values
  */
 function _civicrm_api3_separate_values(&$values) {
   $sp = CRM_Core_DAO::VALUE_SEPARATOR;
@@ -335,7 +374,7 @@ function _civicrm_api3_separate_values(&$values) {
       _civicrm_api3_separate_values($value);
     }
     elseif (is_string($value)) {
-      if($key == 'case_type_id'){// this is to honor the way case API was originally written
+      if ($key == 'case_type_id') {// this is to honor the way case API was originally written
         $value = trim(str_replace($sp, ',', $value), ',');
       }
       elseif (strpos($value, $sp) !== FALSE) {
@@ -356,8 +395,8 @@ function _civicrm_api3_separate_values(&$values) {
  * @param array $params
  * @param array $values
  */
-function _civicrm_api3_filter_fields_for_bao($entity, &$params, &$values){
-  $fields = civicrm_api($entity,'getfields', array('version' => 3,'action' => 'create'));
+function _civicrm_api3_filter_fields_for_bao($entity, &$params, &$values) {
+  $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => 'create'));
   $fields = $fields['values'];
   _civicrm_api3_store_values($fields, $params, $values);
 }
@@ -367,7 +406,7 @@ function _civicrm_api3_filter_fields_for_bao($entity, &$params, &$values){
  * @param array $params
  * @param array $values
  *
- * @return Bool $valueFound
+ * @return Bool
  */
 function _civicrm_api3_store_values(&$fields, &$params, &$values) {
   $valueFound = FALSE;
@@ -390,17 +429,19 @@ 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
+ * 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 $params
+ *   As passed into api get or getcount function.
  * @param array $additional_options
- * @param bool $getCount are we just after the count
+ *   Array of options (so we can modify the filter).
+ * @param bool $getCount
+ *   Are we just after the count.
  *
- * @return
- * @internal param array $options array of options (so we can modify the filter)
+ * @return array
  */
-function _civicrm_api3_get_using_query_object($entity, $params, $additional_options = array(), $getCount = NULL){
+function _civicrm_api3_get_using_query_object($entity, $params, $additional_options = array(), $getCount = NULL) {
 
   // Convert id to e.g. contact_id
   if (empty($params[$entity . '_id']) && isset($params['id'])) {
@@ -418,17 +459,17 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti
     CRM_Utils_Array::value('return', $options, array()),
     CRM_Utils_Array::value('return', $additional_options, array())
   );
-  if(empty($returnProperties)){
+  if (empty($returnProperties)) {
     $returnProperties = NULL;
   }
-  if(!empty($params['check_permissions'])){
+  if (!empty($params['check_permissions'])) {
     // we will filter query object against getfields
     $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => 'get'));
     // we need to add this in as earlier in this function 'id' was unset in favour of $entity_id
     $fields['values'][$entity . '_id'] = array();
     $varsToFilter = array('returnProperties', 'inputParams');
-    foreach ($varsToFilter as $varToFilter){
-      if(!is_array($$varToFilter)){
+    foreach ($varsToFilter as $varToFilter) {
+      if (!is_array($$varToFilter)) {
         continue;
       }
       //I was going to throw an exception rather than silently filter out - but
@@ -438,43 +479,46 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti
       $$varToFilter = array_intersect_key($$varToFilter, $fields['values']);
     }
   }
-  $options = array_merge($options,$additional_options);
+  $options = array_merge($options, $additional_options);
   $sort             = CRM_Utils_Array::value('sort', $options, NULL);
   $offset             = CRM_Utils_Array::value('offset', $options, NULL);
   $limit             = CRM_Utils_Array::value('limit', $options, NULL);
   $smartGroupCache  = CRM_Utils_Array::value('smartGroupCache', $params);
 
-  if($getCount){
+  if ($getCount) {
     $limit = NULL;
     $returnProperties = NULL;
   }
 
   $newParams = CRM_Contact_BAO_Query::convertFormValues($inputParams);
   foreach ($newParams as &$newParam) {
-    if($newParam[1] == '='  && is_array($newParam[2])) {
+    if ($newParam[1] == '='  && is_array($newParam[2])) {
       // we may be looking at an attempt to use the 'IN' style syntax
       // @todo at time of writing only 'IN' & 'NOT IN' are  supported for the array style syntax
       $sqlFilter = CRM_Core_DAO::createSqlFilter($newParam[0], $params[$newParam[0]], 'String', NULL, TRUE);
-      if($sqlFilter) {
+      if ($sqlFilter) {
         $newParam[1] = key($newParam[2]);
         $newParam[2] = $sqlFilter;
       }
     }
 
   }
-  $skipPermissions = CRM_Utils_Array::value('check_permissions', $params)? 0 :1;
+
+  $skipPermissions = !empty($params['check_permissions']) ? 0 : 1;
+
   list($entities, $options) = CRM_Contact_BAO_Query::apiQuery(
     $newParams,
     $returnProperties,
     NULL,
     $sort,
-    $offset ,
+    $offset,
     $limit,
     $smartGroupCache,
     $getCount,
     $skipPermissions
   );
-  if ($getCount) { // only return the count of contacts
+  if ($getCount) {
+    // only return the count of contacts
     return $entities;
   }
 
@@ -489,22 +533,24 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti
  * @param array $params
  * @param string $mode
  * @param string $entity
- * @return CRM_Core_DAO query object
+ * @return array
+ *   [CRM_Core_DAO|CRM_Contact_BAO_Query]
  */
 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());
+  $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);
+  $newParams = CRM_Contact_BAO_Query::convertFormValues($inputParams, 0, FALSE, $entity);
   $query = new CRM_Contact_BAO_Query($newParams, $returnProperties, NULL,
-    FALSE, FALSE, $mode
+    FALSE, FALSE, $mode,
+    empty($params['check_permissions'])
   );
   list($select, $from, $where, $having) = $query->query();
 
@@ -513,7 +559,7 @@ function _civicrm_api3_get_query_object($params, $mode, $entity) {
   if (!empty($sort)) {
     $sql .= " ORDER BY $sort ";
   }
-  if(!empty($rowCount)) {
+  if (!empty($rowCount)) {
     $sql .= " LIMIT $offset, $rowCount ";
   }
   $dao = CRM_Core_DAO::executeQuery($sql);
@@ -522,17 +568,22 @@ function _civicrm_api3_get_query_object($params, $mode, $entity) {
 
 /**
  * Function transfers the filters being passed into the DAO onto the params object
+ * @param CRM_Core_DAO $dao
+ * @param array $params
+ * @param bool $unique
+ * @param string $entity
+ *
+ * @throws API_Exception
+ * @throws Exception
  */
 function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
   $entity = substr($dao->__table, 8);
-
-  $allfields = _civicrm_api3_build_fields_array($dao, $unique);
-
-  $fields = array_intersect(array_keys($allfields), array_keys($params));
-  if (isset($params[$entity . "_id"])) {
+  if (!empty($params[$entity . "_id"]) && empty($params['id'])) {
     //if entity_id is set then treat it as ID (will be overridden by id if set)
-    $dao->id = $params[$entity . "_id"];
+    $params['id'] = $params[$entity . "_id"];
   }
+  $allfields = _civicrm_api3_build_fields_array($dao, $unique);
+  $fields = array_intersect(array_keys($allfields), array_keys($params));
 
   $options = _civicrm_api3_get_options_from_params($params);
   //apply options like sort
@@ -554,9 +605,6 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
       }
     }
   }
-  // http://issues.civicrm.org/jira/browse/CRM-9150 - stick with 'simple' operators for now
-  // support for other syntaxes is discussed in ticket but being put off for now
-  $acceptedSQLOperators = array('=', '<=', '>=', '>', '<', 'LIKE', "<>", "!=", "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN');
   if (!$fields) {
     $fields = array();
   }
@@ -566,7 +614,7 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
       //get the actual fieldname from db
       $fieldName = $allfields[$field]['name'];
       $where = CRM_Core_DAO::createSqlFilter($fieldName, $params[$field], 'String');
-      if(!empty($where)) {
+      if (!empty($where)) {
         $dao->whereAdd($where);
       }
     }
@@ -586,7 +634,7 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
   if (!empty($options['return']) && is_array($options['return']) && empty($options['is_count'])) {
     $dao->selectAdd();
     $options['return']['id'] = TRUE;// ensure 'id' is included
-    $allfields =  _civicrm_api3_get_unique_name_array($dao);
+    $allfields = _civicrm_api3_get_unique_name_array($dao);
     $returnMatched = array_intersect(array_keys($options['return']), $allfields);
     foreach ($returnMatched as $returnValue) {
       $dao->selectAdd($returnValue);
@@ -601,7 +649,7 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
       $unmatchedFields,
       array_flip($allfields)// but a match for the field keys
     );
-    foreach ($returnUniqueMatched as $uniqueVal){
+    foreach ($returnUniqueMatched as $uniqueVal) {
       $dao->selectAdd($allfields[$uniqueVal]);
     }
   }
@@ -610,9 +658,12 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
 
 /**
  * Apply filters (e.g. high, low) to DAO object (prior to find)
- * @param string $filterField field name of filter
- * @param string $filterValue field value of filter
- * @param object $dao DAO object
+ * @param string $filterField
+ *   Field name of filter.
+ * @param string $filterValue
+ *   Field value of filter.
+ * @param object $dao
+ *   DAO object.
  */
 function _civicrm_api3_apply_filters_to_dao($filterField, $filterValue, &$dao) {
   if (strstr($filterField, 'high')) {
@@ -623,11 +674,11 @@ function _civicrm_api3_apply_filters_to_dao($filterField, $filterValue, &$dao) {
     $fieldName = substr($filterField, 0, -4);
     $dao->whereAdd("($fieldName >= $filterValue )");
   }
-  if($filterField == 'is_current' && $filterValue == 1){
+  if ($filterField == 'is_current' && $filterValue == 1) {
     $todayStart = date('Ymd000000', strtotime('now'));
     $todayEnd = date('Ymd235959', strtotime('now'));
     $dao->whereAdd("(start_date <= '$todayStart' OR start_date IS NULL) AND (end_date >= '$todayEnd' OR end_date IS NULL)");
-    if(property_exists($dao, 'is_active')){
+    if (property_exists($dao, 'is_active')) {
       $dao->whereAdd('is_active = 1');
     }
   }
@@ -637,14 +688,18 @@ function _civicrm_api3_apply_filters_to_dao($filterField, $filterValue, &$dao) {
  * Get sort, limit etc options from the params - supporting old & new formats.
  * get returnproperties for legacy
  *
- * @param array $params params array as passed into civicrm_api
- * @param bool $queryObject - is this supporting a queryobject api (e.g contact) - if so we support more options
- * for legacy report & return a unique fields array
+ * @param array $params
+ *   Params array as passed into civicrm_api.
+ * @param bool $queryObject
+ *   Is this supporting a queryobject api (e.g contact) - if so we support more options.
+ *   for legacy report & return a unique fields array
  *
  * @param string $entity
  * @param string $action
  *
- * @return array $options options extracted from params
+ * @throws API_Exception
+ * @return array
+ *   options extracted from params
  */
 function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $entity = '', $action = '') {
   $is_count = FALSE;
@@ -680,16 +735,16 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
       $returnProperties = array_fill_keys($returnProperties, 1);
     }
   }
-  if ($entity && $action =='get') {
-    if (CRM_Utils_Array::value('id',$returnProperties)) {
+  if ($entity && $action == 'get') {
+    if (!empty($returnProperties['id'])) {
       $returnProperties[$entity . '_id'] = 1;
       unset($returnProperties['id']);
     }
-    switch (trim(strtolower($sort))){
-    case 'id':
-    case 'id desc':
-    case 'id asc':
-      $sort = str_replace('id', $entity . '_id',$sort);
+    switch (trim(strtolower($sort))) {
+      case 'id':
+      case 'id desc':
+      case 'id asc':
+        $sort = str_replace('id', $entity . '_id', $sort);
     }
   }
 
@@ -698,7 +753,7 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
     'sort' => CRM_Utils_Rule::string($sort) ? $sort : NULL,
     'limit' => CRM_Utils_Rule::integer($limit) ? $limit : NULL,
     'is_count' => $is_count,
-    'return' => !empty($returnProperties) ? $returnProperties : NULL,
+    'return' => !empty($returnProperties) ? $returnProperties : array(),
   );
 
   if ($options['sort'] && stristr($options['sort'], 'SELECT')) {
@@ -713,16 +768,17 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
   $inputParams = array();
   $legacyreturnProperties = array();
   $otherVars = array(
-    'sort', 'offset', 'rowCount', 'options','return',
+    'sort', 'offset', 'rowCount', 'options', 'return',
   );
   foreach ($params as $n => $v) {
     if (substr($n, 0, 7) == 'return.') {
       $legacyreturnProperties[substr($n, 7)] = $v;
     }
     elseif ($n == 'id') {
-      $inputParams[$entity. '_id'] = $v;
+      $inputParams[$entity . '_id'] = $v;
+    }
+    elseif (in_array($n, $otherVars)) {
     }
-    elseif (in_array($n, $otherVars)) {}
     else {
       $inputParams[$n] = $v;
       if ($v && !is_array($v) && stristr($v, 'SELECT')) {
@@ -738,16 +794,18 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
 /**
  * Apply options (e.g. sort, limit, order by) to DAO object (prior to find)
  *
- * @param array $params params array as passed into civicrm_api
- * @param object $dao DAO object
+ * @param array $params
+ *   Params array as passed into civicrm_api.
+ * @param object $dao
+ *   DAO object.
  * @param $entity
  */
 function _civicrm_api3_apply_options_to_dao(&$params, &$dao, $entity) {
 
-  $options = _civicrm_api3_get_options_from_params($params,FALSE,$entity);
-  if(!$options['is_count']) {
-    if(!empty($options['limit'])) {
-      $dao->limit((int)$options['offset'], (int)$options['limit']);
+  $options = _civicrm_api3_get_options_from_params($params, FALSE, $entity);
+  if (!$options['is_count']) {
+    if (!empty($options['limit'])) {
+      $dao->limit((int) $options['offset'], (int) $options['limit']);
     }
     if (!empty($options['sort'])) {
       $dao->orderBy($options['sort']);
@@ -758,14 +816,19 @@ function _civicrm_api3_apply_options_to_dao(&$params, &$dao, $entity) {
 /**
  * build fields array. This is the array of fields as it relates to the given DAO
  * returns unique fields as keys by default but if set but can return by DB fields
+ *
+ * @param CRM_Core_DAO $bao
+ * @param bool $unique
+ *
+ * @return array
  */
 function _civicrm_api3_build_fields_array(&$bao, $unique = TRUE) {
   $fields = $bao->fields();
   if ($unique) {
-    if(!CRM_Utils_Array::value('id', $fields)){
-     $entity = _civicrm_api_get_entity_name_from_dao($bao);
-     $fields['id'] = $fields[$entity . '_id'];
-     unset($fields[$entity . '_id']);
+    if (empty($fields['id'])) {
+      $entity = _civicrm_api_get_entity_name_from_dao($bao);
+      $fields['id'] = $fields[$entity . '_id'];
+      unset($fields[$entity . '_id']);
     }
     return $fields;
   }
@@ -779,11 +842,15 @@ function _civicrm_api3_build_fields_array(&$bao, $unique = TRUE) {
 /**
  * build fields array. This is the array of fields as it relates to the given DAO
  * returns unique fields as keys by default but if set but can return by DB fields
+ *
+ * @param CRM_Core_DAO $bao
+ *
+ * @return array
  */
 function _civicrm_api3_get_unique_name_array(&$bao) {
   $fields = $bao->fields();
   foreach ($fields as $field => $values) {
-    $uniqueFields[$field] = CRM_Utils_Array::value('name',$values, $field);
+    $uniqueFields[$field] = CRM_Utils_Array::value('name', $values, $field);
   }
   return $uniqueFields;
 }
@@ -791,21 +858,18 @@ function _civicrm_api3_get_unique_name_array(&$bao) {
 /**
  * Converts an DAO object to an array
  *
- * @param  object $dao           (reference )object to convert
- * @param null $params
+ * @param CRM_Core_DAO $dao
+ *   Object to convert.
+ * @param array $params
  * @param bool $uniqueFields
  * @param string $entity
+ * @param bool $autoFind
  *
  * @return array
- *
- * @params array of arrays (key = id) of array of fields
- *
- * @static void
- * @access public
  */
 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'])) {
+  if (isset($params['options']) && !empty($params['options']['is_count'])) {
     return $dao->count();
   }
   if (empty($dao)) {
@@ -815,18 +879,9 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE,
     return array();
   }
 
-  if(isset($dao->count)) {
+  if (isset($dao->count)) {
     return $dao->count;
   }
-  //if custom fields are required we will endeavour to set them . NB passing $entity in might be a bit clunky / unrequired
-  if (!empty($entity) && CRM_Utils_Array::value('return', $params) && is_array($params['return'])) {
-    foreach ($params['return'] as $return) {
-      if (substr($return, 0, 6) == 'custom') {
-        $custom = TRUE;
-      }
-    }
-  }
-
 
   $fields = array_keys(_civicrm_api3_build_fields_array($dao, $uniqueFields));
 
@@ -841,25 +896,45 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE,
       }
     }
     $result[$dao->id] = $tmp;
-    if (!empty($custom)) {
+
+    if (_civicrm_api3_custom_fields_are_required($entity, $params)) {
       _civicrm_api3_custom_data_get($result[$dao->id], $entity, $dao->id);
     }
   }
 
-
   return $result;
 }
 
+/**
+ * We currently retrieve all custom fields or none at this level so if we know the entity
+ * && it can take custom fields & there is the string 'custom' in their return request we get them all, they are filtered on the way out
+ * @todo filter so only required fields are queried
+ *
+ * @param string $entity
+ *   Entity name in CamelCase.
+ * @param array $params
+ *
+ * @return bool
+ */
+function _civicrm_api3_custom_fields_are_required($entity, $params) {
+  if (!array_key_exists($entity, CRM_Core_BAO_CustomQuery::$extendsMap)) {
+    return FALSE;
+  }
+  $options = _civicrm_api3_get_options_from_params($params);
+  //we check for possibility of 'custom' => 1 as well as specific custom fields
+  $returnString = implode('', $options['return']) . implode('', array_keys($options['return']));
+  if (stristr($returnString, 'custom')) {
+    return TRUE;
+  }
+}
 /**
  * Converts an object to an array
  *
- * @param  object $dao           (reference) object to convert
- * @param  array $values        (reference) array
+ * @param object $dao
+ *   (reference) object to convert.
+ * @param array $values
+ *   (reference) array.
  * @param array|bool $uniqueFields
- *
- * @return array
- * @static void
- * @access public
  */
 function _civicrm_api3_object_to_array(&$dao, &$values, $uniqueFields = FALSE) {
 
@@ -873,6 +948,9 @@ function _civicrm_api3_object_to_array(&$dao, &$values, $uniqueFields = FALSE) {
 
 /**
  * Wrapper for _civicrm_object_to_array when api supports unique fields
+ * @param $dao
+ * @param $values
+ * @return array
  */
 function _civicrm_api3_object_to_array_unique_fields(&$dao, &$values) {
   return _civicrm_api3_object_to_array($dao, $values, TRUE);
@@ -882,19 +960,21 @@ function _civicrm_api3_object_to_array_unique_fields(&$dao, &$values) {
  *
  * @param array $params
  * @param array $values
- * @param string $extends entity that this custom field extends (e.g. contribution, event, contact)
- * @param string $entityId ID of entity per $extends
+ * @param string $extends
+ *   Entity that this custom field extends (e.g. contribution, event, contact).
+ * @param string $entityId
+ *   ID of entity per $extends.
  */
 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'))) {
+  if (in_array($extends, array('Household', 'Individual', 'Organization'))) {
     $entity = 'Contact';
   }
 
   $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => 'create'));
-  if(!$fields['is_error']) {
+  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;
@@ -902,17 +982,34 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity
 
   foreach ($params as $key => $value) {
     list($customFieldID, $customValueID) = CRM_Core_BAO_CustomField::getKeyID($key, TRUE);
-    if ($customFieldID && (!IS_NULL($value))) {
-      if ($checkCheckBoxField && $fields['custom_' . $customFieldID]['html_type'] == 'CheckBox') {
+    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
+        $value, $extends, $customValueID, $entityId, FALSE, FALSE, TRUE
       );
     }
   }
 }
 
+/**
+ * @param array $params
+ * @param $entity
+ */
+function _civicrm_api3_format_params_for_create(&$params, $entity) {
+  $nonGenericEntities = array('Contact', 'Individual', 'Household', 'Organization');
+
+  $customFieldEntities = array_diff_key(CRM_Core_BAO_CustomQuery::$extendsMap, array_fill_keys($nonGenericEntities, 1));
+  if (!array_key_exists($entity, $customFieldEntities)) {
+    return;
+  }
+  $values = array();
+  _civicrm_api3_custom_format_params($params, $values, $entity);
+  $params = array_merge($params, $values);
+}
+
 /**
  * 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
@@ -921,6 +1018,14 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity
  * 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
  *
@@ -928,10 +1033,6 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity
  * @param $customFieldLabel
  * @param $entity
  *
- * @internal param $fields
- * @internal param $customFieldID
- * @internal param $checkCheckBoxField
- * @return string
  */
 function formatCheckBoxField(&$checkboxFieldValue, $customFieldLabel, $entity) {
 
@@ -978,7 +1079,9 @@ function formatCheckBoxField(&$checkboxFieldValue, $customFieldLabel, $entity) {
   }
   elseif (stristr($checkboxFieldValue, ',')) {
     $formatValue = TRUE;
-    //lets see if we should separate it
+    //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 {
@@ -1012,17 +1115,17 @@ function formatCheckBoxField(&$checkboxFieldValue, $customFieldLabel, $entity) {
  * api level. Hence the intention is to remove this function
  * & the associated param from viery_mandatory
  *
- * @param array $params       Associative array of property name/value
+ * @param array $params
+ *   Associative array of property name/value.
  *                             pairs to insert in new history.
- * @param $daoName
+ * @param string $daoName
  * @param bool $return
  *
  * @daoName string DAO to check params agains
  *
- * @return bool should the missing fields be returned as an array (core error created as default)
- *
- * @return bool true if all fields present, depending on $result a core error is created of an array of missing fields is returned
- * @access public
+ * @return bool
+ *   Sshould the missing fields be returned as an array (core error created as default)
+ *   true if all fields present, depending on $result a core error is created of an array of missing fields is returned
  */
 function _civicrm_api3_check_required_fields($params, $daoName, $return = FALSE) {
   //@deprecated - see notes
@@ -1048,7 +1151,7 @@ function _civicrm_api3_check_required_fields($params, $daoName, $return = FALSE)
       continue;
     }
 
-    if (CRM_Utils_Array::value('required', $v)) {
+    if (!empty($v['required'])) {
       // 0 is a valid input for numbers, CRM-8122
       if (!isset($params[$k]) || (empty($params[$k]) && !($params[$k] === 0))) {
         $missing[] = $k;
@@ -1068,54 +1171,15 @@ function _civicrm_api3_check_required_fields($params, $daoName, $return = FALSE)
   return TRUE;
 }
 
-/**
- * Check permissions for a given API call.
- *
- * @param $entity string API entity being accessed
- * @param $action string API action being performed
- * @param $params array  params of the API call
- * @param $throw deprecated bool    whether to throw exception instead of returning false
- *
- * @throws Exception
- * @return bool whether the current API user has the permission to make the call
- */
-function _civicrm_api3_api_check_permission($entity, $action, &$params, $throw = TRUE) {
-  // return early unless we’re told explicitly to do the permission check
-  if (empty($params['check_permissions']) or $params['check_permissions'] == FALSE) {
-    return TRUE;
-  }
-
-  require_once 'CRM/Core/DAO/permissions.php';
-  $permissions = _civicrm_api3_permissions($entity, $action, $params);
-
-  // $params might’ve been reset by the alterAPIPermissions() hook
-  if (isset($params['check_permissions']) and $params['check_permissions'] == FALSE) {
-    return TRUE;
-  }
-
-  if (!CRM_Core_Permission::check($permissions)) {
-    if ($throw) {
-      if(is_array($permissions)) {
-        $permissions = implode(' and ', $permissions);
-      }
-      throw new Exception("API permission check failed for $entity/$action call; insufficient permission: require $permissions");
-    }
-    else {
-      //@todo remove this - this is an internal api function called with $throw set to TRUE. It is only called with false
-      // in tests & that should be tidied up
-      return FALSE;
-    }
-  }
-
-  return TRUE;
-}
-
 /**
  * Function to do a 'standard' api get - when the api is only doing a $bao->find then use this
  *
- * @param string $bao_name name of BAO
- * @param array $params params from api
- * @param bool $returnAsSuccess return in api success format
+ * @param string $bao_name
+ *   Name of BAO.
+ * @param array $params
+ *   Params from api.
+ * @param bool $returnAsSuccess
+ *   Return in api success format.
  * @param string $entity
  *
  * @return array
@@ -1124,22 +1188,28 @@ function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $
   $bao = new $bao_name();
   _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, 'get');
+    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);
+    return _civicrm_api3_dao_to_array($bao, $params, FALSE, $entity, 'get');
   }
 }
 
 /**
  * Function to do a 'standard' api create - when the api is only doing a $bao::create then use this
- * @param string $bao_name Name of BAO Class
- * @param array $params parameters passed into the api call
- * @param string $entity Entity - pass in if entity is non-standard & required $ids array
+ *
+ * @param string $bao_name
+ *   Name of BAO Class.
+ * @param array $params
+ *   Parameters passed into the api call.
+ * @param string $entity
+ *   Entity - pass in if entity is non-standard & required $ids array.
+ *
+ * @throws API_Exception
  * @return array
  */
 function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) {
-
+  _civicrm_api3_format_params_for_create($params, $entity);
   $args = array(&$params);
   if (!empty($entity)) {
     $ids = array($entity => CRM_Utils_Array::value('id', $params));
@@ -1194,7 +1264,11 @@ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) {
  * @return CRM_Core_DAO|NULL an instance of the BAO
  */
 function _civicrm_api3_basic_create_fallback($bao_name, &$params) {
-  $entityName = CRM_Core_DAO_AllCoreTables::getBriefName(get_parent_class($bao_name));
+  $dao_name = get_parent_class($bao_name);
+  if ($dao_name === 'CRM_Core_DAO' || !$dao_name) {
+    $dao_name = $bao_name;
+  }
+  $entityName = CRM_Core_DAO_AllCoreTables::getBriefName($dao_name);
   if (empty($entityName)) {
     throw new API_Exception("Class \"$bao_name\" does not map to an entity name", "unmapped_class_to_entity", array(
       'class_name' => $bao_name,
@@ -1203,7 +1277,7 @@ function _civicrm_api3_basic_create_fallback($bao_name, &$params) {
   $hook = empty($params['id']) ? 'create' : 'edit';
 
   CRM_Utils_Hook::pre($hook, $entityName, CRM_Utils_Array::value('id', $params), $params);
-  $instance = new $bao_name();
+  $instance = new $dao_name();
   $instance->copyValues($params);
   $instance->save();
   CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance);
@@ -1214,6 +1288,13 @@ function _civicrm_api3_basic_create_fallback($bao_name, &$params) {
 /**
  * Function to do a 'standard' api del - when the api is only doing a $bao::del then use this
  * if api::del doesn't exist it will try DAO delete method
+ *
+ * @param string $bao_name
+ * @param array $params
+ *
+ * @return array
+ *   API result array
+ * @throws API_Exception
  */
 function _civicrm_api3_basic_delete($bao_name, &$params) {
 
@@ -1247,15 +1328,20 @@ function _civicrm_api3_basic_delete($bao_name, &$params) {
  * Get custom data for the given entity & Add it to the returnArray as 'custom_123' = 'custom string' AND 'custom_123_1' = 'custom string'
  * Where 123 is field value & 1 is the id within the custom group data table (value ID)
  *
- * @param array $returnArray - array to append custom data too - generally $result[4] where 4 is the entity id.
- * @param string $entity  e.g membership, event
- * @param $entity_id
- * @param int $groupID - per CRM_Core_BAO_CustomGroup::getTree
- * @param int $subType e.g. membership_type_id where custom data doesn't apply to all membership types
- * @param string $subName - Subtype of entity
+ * @param array $returnArray
+ *   Array to append custom data too - generally $result[4] where 4 is the entity id.
+ * @param string $entity
+ *   E.g membership, event.
+ * @param int $entity_id
+ * @param int $groupID
+ *   Per CRM_Core_BAO_CustomGroup::getTree.
+ * @param int $subType
+ *   E.g. membership_type_id where custom data doesn't apply to all membership types.
+ * @param string $subName
+ *   Subtype of entity.
  */
 function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $groupID = NULL, $subType = NULL, $subName = NULL) {
-  $groupTree = &CRM_Core_BAO_CustomGroup::getTree($entity,
+  $groupTree = CRM_Core_BAO_CustomGroup::getTree($entity,
     CRM_Core_DAO::$_nullObject,
     $entity_id,
     $groupID,
@@ -1265,19 +1351,23 @@ function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $grou
   $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1, CRM_Core_DAO::$_nullObject);
   $customValues = array();
   CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $customValues);
+  $fieldInfo = array();
+  foreach ($groupTree as $set) {
+    $fieldInfo += $set['fields'];
+  }
   if (!empty($customValues)) {
     foreach ($customValues as $key => $val) {
-      if (strstr($key, '_id')) {
-        $idkey = substr($key, 0, -3);
-        $returnArray['custom_' . (CRM_Core_BAO_CustomField::getKeyID($idkey) . "_id")] = $val;
-        $returnArray[$key] = $val;
-      }
-      else {
-        // per standard - return custom_fieldID
-        $returnArray['custom_' . (CRM_Core_BAO_CustomField::getKeyID($key))] = $val;
+      // per standard - return custom_fieldID
+      $id = CRM_Core_BAO_CustomField::getKeyID($key);
+      $returnArray['custom_' . $id] = $val;
+
+      //not standard - but some api did this so guess we should keep - cheap as chips
+      $returnArray[$key] = $val;
 
-        //not standard - but some api did this so guess we should keep - cheap as chips
-        $returnArray[$key] = $val;
+      // Shim to restore legacy behavior of ContactReference custom fields
+      if (!empty($fieldInfo[$id]) && $fieldInfo[$id]['data_type'] == 'ContactReference') {
+        $returnArray['custom_' . $id . '_id'] = $returnArray[$key . '_id'] = $val;
+        $returnArray['custom_' . $id] = $returnArray[$key] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $val, 'sort_name');
       }
     }
   }
@@ -1291,12 +1381,15 @@ function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $grou
  * As of writing only date was implemented.
  * @param string $entity
  * @param string $action
- * @param array $params -
- * @param array $fields response from getfields all variables are the same as per civicrm_api
- * @param bool $errorMode errorMode do intensive post fail checks?
+ * @param array $params
+ *   -.
+ * @param array $fields
+ *   Response from getfields all variables are the same as per civicrm_api.
+ * @param bool $errorMode
+ *   ErrorMode do intensive post fail checks?.
  * @throws Exception
  */
-function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $errorMode = False) {
+function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $errorMode = FALSE) {
   $fields = array_intersect_key($fields, $params);
   foreach ($fields as $fieldName => $fieldInfo) {
     switch (CRM_Utils_Array::value('type', $fieldInfo)) {
@@ -1307,11 +1400,13 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $err
 
       case 4:
       case 12:
+      case CRM_Utils_Type::T_TIMESTAMP:
         //field is of type date or datetime
         _civicrm_api3_validate_date($params, $fieldName, $fieldInfo);
         break;
 
-    case 32://blob
+      case 32:
+        //blob
         _civicrm_api3_validate_html($params, $fieldName, $fieldInfo);
         break;
 
@@ -1320,24 +1415,31 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $err
         break;
 
       case CRM_Utils_Type::T_MONEY:
-        if (!CRM_Utils_Rule::money($params[$fieldName]) && !empty($params[$fieldName])) {
-          throw new Exception($fieldName . " is  not a valid amount: " . $params[$fieldName]);
+        list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+        if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) {
+          break;
         }
+        foreach ((array) $fieldValue as $fieldvalue) {
+          if (!CRM_Utils_Rule::money($fieldvalue) && !empty($fieldvalue)) {
+            throw new Exception($fieldName . " is  not a valid amount: " . $params[$fieldName]);
+          }
+        }
+        break;
     }
 
     // intensive checks - usually only called after DB level fail
     if (!empty($errorMode) && strtolower($action) == 'create') {
-      if (CRM_Utils_Array::value('FKClassName', $fieldInfo)) {
-        if (CRM_Utils_Array::value($fieldName, $params)) {
+      if (!empty($fieldInfo['FKClassName'])) {
+        if (!empty($fieldValue)) {
           _civicrm_api3_validate_constraint($params, $fieldName, $fieldInfo);
         }
-        elseif (CRM_Utils_Array::value('required', $fieldInfo)) {
+        elseif (!empty($fieldInfo['required'])) {
           throw new Exception("DB Constraint Violation - possibly $fieldName should possibly be marked as mandatory for this API. If so, please raise a bug report");
         }
       }
-      if (CRM_Utils_Array::value('api.unique', $fieldInfo)) {
+      if (!empty($fieldInfo['api.unique'])) {
         $params['entity'] = $entity;
-        _civicrm_api3_validate_uniquekey($params, $fieldName, $fieldInfo);
+        _civicrm_api3_validate_unique_key($params, $fieldName);
       }
     }
   }
@@ -1353,68 +1455,103 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $err
  * It also checks against the RULE:date function. This is a centralisation of code that was scattered and
  * may not be the best thing to do. There is no code level documentation on the existing functions to work off
  *
- * @param array $params params from civicrm_api
- * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
+ * @param array $params
+ *   Params from civicrm_api.
+ * @param string $fieldName
+ *   Uniquename of field being checked.
+ * @param array $fieldInfo
+ *   Array of fields from getfields function.
  * @throws Exception
- * @internal param array $fieldinfo array of fields from getfields function
  */
 function _civicrm_api3_validate_date(&$params, &$fieldName, &$fieldInfo) {
+  list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+  if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) {
+    return;
+  }
   //should we check first to prevent it from being copied if they have passed in sql friendly format?
-  if (CRM_Utils_Array::value($fieldInfo['name'], $params)) {
-    //accept 'whatever strtotime accepts
-    if (strtotime($params[$fieldInfo['name']]) === FALSE) {
-      throw new Exception($fieldInfo['name'] . " is not a valid date: " . $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 (!empty($params[$fieldInfo['name']])) {
+    $fieldValue = _civicrm_api3_getValidDate($fieldValue, $fieldInfo['name'], $fieldInfo['type']);
+  }
+  if ((CRM_Utils_Array::value('name', $fieldInfo) != $fieldName) && !empty($fieldValue)) {
+    $fieldValue = _civicrm_api3_getValidDate($fieldValue, $fieldName, $fieldInfo['type']);
+  }
+
+  if (!empty($op)) {
+    $params[$fieldName][$op] = $fieldValue;
+  }
+  else {
+    $params[$fieldName] = $fieldValue;
   }
-  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
-    if (strtotime($params[$fieldName]) === FALSE) {
-      throw new Exception($fieldName . " is not a valid date: " . $params[$fieldName]);
+}
+
+/**
+ * convert date into BAO friendly date
+ * we accept 'whatever strtotime accepts'
+ *
+ * @param string $dateValue
+ * @param string $fieldName
+ * @param $fieldType
+ *
+ * @throws Exception
+ * @return mixed
+ */
+function _civicrm_api3_getValidDate($dateValue, $fieldName, $fieldType) {
+  if (is_array($dateValue)) {
+    foreach ($dateValue as $key => $value) {
+      $dateValue[$key] = _civicrm_api3_getValidDate($value, $fieldName, $fieldType);
     }
-    $params[$fieldName] = CRM_Utils_Date::processDate($params[$fieldName]);
+    return $dateValue;
   }
+  if (strtotime($dateValue) === FALSE) {
+    throw new Exception($fieldName . " is not a valid date: " . $dateValue);
+  }
+  $format = ($fieldType == CRM_Utils_Type::T_DATE) ? 'Ymd000000' : 'YmdHis';
+  return CRM_Utils_Date::processDate($dateValue, NULL, FALSE, $format);
 }
 
 /**
  * Validate foreign constraint fields being passed into API.
  *
- * @param array $params params from civicrm_api
- * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
- * @throws Exception
- * @internal param array $fieldinfo array of fields from getfields function
+ * @param mixed $fieldValue
+ * @param string $fieldName
+ *   Uniquename of field being checked.
+ * @param array $fieldInfo
+ *   Array of fields from getfields function.
+ * @throws \API_Exception
  */
-function _civicrm_api3_validate_constraint(&$params, &$fieldName, &$fieldInfo) {
-  $dao = new $fieldInfo['FKClassName'];
-  $dao->id = $params[$fieldName];
+function _civicrm_api3_validate_constraint(&$fieldValue, &$fieldName, &$fieldInfo) {
+  $daoName = $fieldInfo['FKClassName'];
+  $dao = new $daoName();
+  $dao->id = $fieldValue;
   $dao->selectAdd();
   $dao->selectAdd('id');
   if (!$dao->find()) {
-    throw new Exception("$fieldName is not valid : " . $params[$fieldName]);
+    throw new API_Exception("$fieldName is not valid : " . $fieldValue);
   }
 }
 
 /**
  * Validate foreign constraint fields being passed into API.
  *
- * @param array $params params from civicrm_api
- * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
+ * @param array $params
+ *   Params from civicrm_api.
+ * @param string $fieldName
+ *   Uniquename of field being checked.
  * @throws Exception
- * @internal param array $fieldinfo array of fields from getfields function
  */
-function _civicrm_api3_validate_uniquekey(&$params, &$fieldName, &$fieldInfo) {
+function _civicrm_api3_validate_unique_key(&$params, &$fieldName) {
+  list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+  if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) {
+    return;
+  }
   $existing = civicrm_api($params['entity'], 'get', array(
       'version' => $params['version'],
-      $fieldName => $params[$fieldName],
+      $fieldName => $fieldValue,
     ));
   // an entry already exists for this unique field
   if ($existing['count'] == 1) {
     // question - could this ever be a security issue?
-    throw new Exception("Field: `$fieldName` must be unique. An conflicting entity already exists - id: " . $existing['id']);
+    throw new API_Exception("Field: `$fieldName` must be unique. An conflicting entity already exists - id: " . $existing['id']);
   }
 }
 
@@ -1427,8 +1564,10 @@ function _civicrm_api3_validate_uniquekey(&$params, &$fieldName, &$fieldInfo) {
  * Note: This will verify that 'values' is present, but it does not directly verify
  * any other parameters.
  *
- * @param string $entity entity name
- * @param array $params params from civicrm_api, including:
+ * @param string $entity
+ *   Entity name.
+ * @param array $params
+ *   Params from civicrm_api, including:.
  *   - 'values': an array of records to save
  *   - all other items: keys which identify new/pre-existing records
  * @return array|int
@@ -1496,7 +1635,7 @@ function _civicrm_api3_generic_replace($entity, $params) {
 }
 
 /**
- * @param $params
+ * @param array $params
  *
  * @return mixed
  */
@@ -1511,14 +1650,15 @@ function _civicrm_api3_generic_replace_base_params($params) {
 /**
  * returns fields allowable by api
  *
- * @param $entity string Entity to query
- * @param bool $unique index by unique fields?
+ * @param $entity
+ *   String Entity to query.
+ * @param bool $unique
+ *   Index by unique fields?.
  * @param array $params
  *
  * @return array
  */
-function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array(
-  )) {
+function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array()) {
   $unsetIfEmpty = array('dataPattern', 'headerPattern', 'default', 'export', 'import');
   $dao = _civicrm_api3_get_DAO($entity);
   if (empty($dao)) {
@@ -1528,7 +1668,7 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array(
   $fields = $d->fields();
   // replace uniqueNames by the normal names as the key
   if (empty($unique)) {
-    foreach ($fields as $name => & $field) {
+    foreach ($fields as $name => &$field) {
       //getting rid of unused attributes
       foreach ($unsetIfEmpty as $attr) {
         if (empty($field[$attr])) {
@@ -1548,6 +1688,15 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array(
       unset($fields[$name]);
     }
   }
+  // Translate FKClassName to the corresponding api
+  foreach ($fields as $name => &$field) {
+    if (!empty($field['FKClassName'])) {
+      $FKApi = CRM_Core_DAO_AllCoreTables::getBriefName($field['FKClassName']);
+      if ($FKApi) {
+        $field['FKApiName'] = $FKApi;
+      }
+    }
+  }
   $fields += _civicrm_api_get_custom_fields($entity, $params);
   return $fields;
 }
@@ -1555,48 +1704,43 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array(
 /**
  * Return an array of fields for a given entity - this is the same as the BAO function but
  * fields are prefixed with 'custom_' to represent api params
+ * @param $entity
+ * @param array $params
+ * @return array
  */
 function _civicrm_api_get_custom_fields($entity, &$params) {
-  $customfields = array();
   $entity = _civicrm_api_get_camel_name($entity);
-  if (strtolower($entity) == 'contact') {
-    // Use sub-type if available, otherwise stick with 'Contact'
+  if ($entity == 'Contact') {
+    // Use sub-type if available, otherwise "NULL" to fetch from all contact types
     $entity = CRM_Utils_Array::value('contact_type', $params);
   }
-  $retrieveOnlyParent = FALSE;
-  // we could / should probably test for other subtypes here - e.g. activity_type_id
-  if($entity == 'Contact'){
-    empty($params['contact_sub_type']);
-  }
   $customfields = CRM_Core_BAO_CustomField::getFields($entity,
     FALSE,
     FALSE,
-    CRM_Utils_Array::value('contact_sub_type', $params, FALSE),
+    // we could / should probably test for other subtypes here - e.g. activity_type_id
+    CRM_Utils_Array::value('contact_sub_type', $params),
     NULL,
-    $retrieveOnlyParent,
+    FALSE,
     FALSE,
     FALSE
   );
-  // find out if we have any requests to resolve options
-  $getoptions = CRM_Utils_Array::value('get_options', CRM_Utils_Array::value('options',$params));
-  if(!is_array($getoptions)){
-      $getoptions = array($getoptions);
-  }
+
+  $ret = array();
 
   foreach ($customfields as $key => $value) {
     // Regular fields have a 'name' property
     $value['name'] = 'custom_' . $key;
+    $value['title'] = $value['label'];
     $value['type'] = _getStandardTypeFromCustomDataType($value['data_type']);
-    $customfields['custom_' . $key] = $value;
-    if (in_array('custom_' . $key, $getoptions)) {
-      $customfields['custom_' . $key]['options'] = CRM_Core_BAO_CustomOption::valuesByID($key);
-    }
-    unset($customfields[$key]);
+    $ret['custom_' . $key] = $value;
   }
-  return $customfields;
+  return $ret;
 }
+
 /**
  * Translate the custom field data_type attribute into a std 'type'
+ * @param $dataType
+ * @return int
  */
 function _getStandardTypeFromCustomDataType($dataType) {
   $mapping = array(
@@ -1615,44 +1759,20 @@ function _getStandardTypeFromCustomDataType($dataType) {
   );
   return $mapping[$dataType];
 }
-/**
- * Return array of defaults for the given API (function is a wrapper on getfields)
- */
-function _civicrm_api3_getdefaults($apiRequest, $fields) {
-  $defaults = array();
-
-  foreach ($fields as $field => $values) {
-    if (isset($values['api.default'])) {
-      $defaults[$field] = $values['api.default'];
-    }
-  }
-  return $defaults;
-}
-
-/**
- * Return array of defaults for the given API (function is a wrapper on getfields)
- */
-function _civicrm_api3_getrequired($apiRequest, $fields) {
-  $required = array('version');
 
-  foreach ($fields as $field => $values) {
-    if (CRM_Utils_Array::value('api.required', $values)) {
-      $required[] = $field;
-    }
-  }
-  return $required;
-}
 
 /**
  * Fill params array with alternate (alias) values where a field has an alias and that is filled & the main field isn't
  * If multiple aliases the last takes precedence
  *
  * Function also swaps unique fields for non-unique fields & vice versa.
+ * @param $apiRequest
+ * @param $fields
  */
 function _civicrm_api3_swap_out_aliases(&$apiRequest, $fields) {
   foreach ($fields as $field => $values) {
     $uniqueName = CRM_Utils_Array::value('uniqueName', $values);
-    if (CRM_Utils_Array::value('api.aliases', $values)) {
+    if (!empty($values['api.aliases'])) {
       // if aliased field is not set we try to use field alias
       if (!isset($apiRequest['params'][$field])) {
         foreach ($values['api.aliases'] as $alias) {
@@ -1665,9 +1785,7 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest, $fields) {
         }
       }
     }
-    if (!isset($apiRequest['params'][$field])
-      && CRM_Utils_Array::value('name', $values)
-      && $field != $values['name']
+    if (!isset($apiRequest['params'][$field]) && !empty($values['name']) && $field != $values['name']
       && isset($apiRequest['params'][$values['name']])
     ) {
       $apiRequest['params'][$field] = $apiRequest['params'][$values['name']];
@@ -1677,8 +1795,7 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest, $fields) {
       && $uniqueName
       && $field != $uniqueName
       && array_key_exists($uniqueName, $apiRequest['params'])
-      )
-    {
+    ) {
       $apiRequest['params'][$field] = CRM_Utils_Array::value($values['uniqueName'], $apiRequest['params']);
       // note that it would make sense to unset the original field here but tests need to be in place first
     }
@@ -1688,57 +1805,70 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest, $fields) {
 
 /**
  * Validate integer fields being passed into API.
- * It currently converts the incoming value 'user_contact_id' into the id of the currenty logged in user
+ * It currently converts the incoming value 'user_contact_id' into the id of the currently logged in user
  *
- * @param array $params params from civicrm_api
- * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
- * @param $entity
+ * @param array $params
+ *   Params from civicrm_api.
+ * @param string $fieldName
+ *   Uniquename of field being checked.
+ * @param array $fieldInfo
+ *   Array of fields from getfields function.
+ * @param string $entity
  * @throws API_Exception
- * @internal param array $fieldinfo array of fields from getfields function
  */
 function _civicrm_api3_validate_integer(&$params, &$fieldName, &$fieldInfo, $entity) {
-  //if fieldname exists in params
-  if (CRM_Utils_Array::value($fieldName, $params)) {
+  list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+  if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) {
+    return;
+  }
+
+  if (!empty($fieldValue)) {
     // if value = 'user_contact_id' (or similar), replace value with contact id
-    if (!is_numeric($params[$fieldName]) && is_scalar($params[$fieldName])) {
-      $realContactId = _civicrm_api3_resolve_contactID($params[$fieldName]);
+    if (!is_numeric($fieldValue) && is_scalar($fieldValue)) {
+      $realContactId = _civicrm_api3_resolve_contactID($fieldValue);
       if ('unknown-user' === $realContactId) {
-        throw new API_Exception("\"$fieldName\" \"{$params[$fieldName]}\" cannot be resolved to a contact ID", 2002, array('error_field' => $fieldName,"type"=>"integer"));
-      } elseif (is_numeric($realContactId)) {
-        $params[$fieldName] = $realContactId;
+        throw new API_Exception("\"$fieldName\" \"{$fieldValue}\" cannot be resolved to a contact ID", 2002, array('error_field' => $fieldName, "type" => "integer"));
+      }
+      elseif (is_numeric($realContactId)) {
+        $fieldValue = $realContactId;
       }
     }
     if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options'])) {
-      _civicrm_api3_api_match_pseudoconstant($params, $entity, $fieldName, $fieldInfo);
+      _civicrm_api3_api_match_pseudoconstant($fieldValue, $entity, $fieldName, $fieldInfo);
     }
 
     // After swapping options, ensure we have an integer(s)
-    foreach ((array) ($params[$fieldName]) as $value) {
+    foreach ((array) ($fieldValue) as $value) {
       if ($value && !is_numeric($value) && $value !== 'null' && !is_array($value)) {
         throw new API_Exception("$fieldName is not a valid integer", 2001, array('error_field' => $fieldName, "type" => "integer"));
       }
     }
 
     // Check our field length
-    if(is_string($params[$fieldName]) &&
-      CRM_Utils_Array::value('maxlength',$fieldInfo)
-      && strlen($params[$fieldName]) > $fieldInfo['maxlength']
-      ){
-      throw new API_Exception( $params[$fieldName] . " is " . strlen($params[$fieldName]) . " characters  - longer than $fieldName length" . $fieldInfo['maxlength'] . ' characters',
-        2100, array('field' => $fieldName, "max_length"=>$fieldInfo['maxlength'])
+    if (is_string($fieldValue) && !empty($fieldInfo['maxlength']) && strlen($fieldValue) > $fieldInfo['maxlength']
+      ) {
+      throw new API_Exception($fieldValue . " is " . strlen($fieldValue) . " characters  - longer than $fieldName length" . $fieldInfo['maxlength'] . ' characters',
+        2100, array('field' => $fieldName, "max_length" => $fieldInfo['maxlength'])
       );
     }
   }
+
+  if (!empty($op)) {
+    $params[$fieldName][$op] = $fieldValue;
+  }
+  else {
+    $params[$fieldName] = $fieldValue;
+  }
 }
 
 /**
  * Determine a contact ID using a string expression
  *
- * @param string $contactIdExpr e.g. "user_contact_id" or "@user:username"
+ * @param string $contactIdExpr
+ *   E.g. "user_contact_id" or "@user:username".
  * @return int|NULL|'unknown-user'
  */
-function  _civicrm_api3_resolve_contactID($contactIdExpr) {
+function _civicrm_api3_resolve_contactID($contactIdExpr) {
   //if value = 'user_contact_id' replace value with logged in user id
   if ($contactIdExpr == "user_contact_id") {
     return CRM_Core_Session::getLoggedInContactID();
@@ -1763,74 +1893,95 @@ function  _civicrm_api3_resolve_contactID($contactIdExpr) {
 
 /**
  * Validate html (check for scripting attack)
- * @param $params
- * @param $fieldName
- * @param $fieldInfo
+ * @param array $params
+ * @param string $fieldName
+ * @param array $fieldInfo
  *
  * @throws API_Exception
  */
-function _civicrm_api3_validate_html(&$params, &$fieldName, &$fieldInfo) {
-  if ($value = CRM_Utils_Array::value($fieldName, $params)) {
-    if (!CRM_Utils_Rule::xssString($value)) {
-      throw new API_Exception('Illegal characters in input (potential scripting attack)', array("field"=>$fieldName,"error_code"=>"xss"));
+function _civicrm_api3_validate_html(&$params, &$fieldName, $fieldInfo) {
+  list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+  if (strpos($op, 'NULL') || strpos($op, 'EMPTY')) {
+    return;
+  }
+  if ($fieldValue) {
+    if (!CRM_Utils_Rule::xssString($fieldValue)) {
+      throw new API_Exception('Illegal characters in input (potential scripting attack)', array("field" => $fieldName, "error_code" => "xss"));
     }
   }
 }
 
 /**
  * Validate string fields being passed into API.
- * @param array $params params from civicrm_api
- * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
- * @param $entity
+ * @param array $params
+ *   Params from civicrm_api.
+ * @param string $fieldName
+ *   Uniquename of field being checked.
+ * @param array $fieldInfo
+ *   Array of fields from getfields function.
+ * @param string $entity
  * @throws API_Exception
  * @throws Exception
- * @internal param array $fieldinfo array of fields from getfields function
  */
 function _civicrm_api3_validate_string(&$params, &$fieldName, &$fieldInfo, $entity) {
-  // If fieldname exists in params
-  $value = CRM_Utils_Array::value($fieldName, $params, '');
-  if(!is_array($value)){
-    $value = (string) $value;
+  list($fieldValue, $op) = _civicrm_api3_field_value_check($params, $fieldName);
+  if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE || CRM_Utils_System::isNull($fieldValue)) {
+    return;
   }
-  else{
+
+  if (!is_array($fieldValue)) {
+    $fieldValue = (string) $fieldValue;
+  }
+  else {
     //@todo what do we do about passed in arrays. For many of these fields
     // the missing piece of functionality is separating them to a separated string
     // & many save incorrectly. But can we change them wholesale?
   }
-  if ($value ) {
-    if (!CRM_Utils_Rule::xssString($value)) {
-      throw new Exception('Illegal characters in input (potential scripting attack)');
-    }
-    if ($fieldName == 'currency') {
-      if (!CRM_Utils_Rule::currencyCode($value)) {
-        throw new Exception("Currency not a valid code: $value");
+  if ($fieldValue) {
+    foreach ((array) $fieldValue as $value) {
+      if (!CRM_Utils_Rule::xssString($fieldValue)) {
+        throw new Exception('Illegal characters in input (potential scripting attack)');
+      }
+      if ($fieldName == 'currency') {
+        //When using IN operator $fieldValue is a array of currency codes
+        if (!CRM_Utils_Rule::currencyCode($value)) {
+          throw new Exception("Currency not a valid code: $currency");
+        }
       }
     }
-    if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options']) || !empty($fieldInfo['enumValues'])) {
-      _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.",
-        2100, array('field' => $fieldName)
-      );
-    }
+  }
+  if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options'])) {
+    _civicrm_api3_api_match_pseudoconstant($fieldValue, $entity, $fieldName, $fieldInfo);
+  }
+  // Check our field length
+  elseif (is_string($fieldValue) && !empty($fieldInfo['maxlength']) && strlen(utf8_decode($fieldValue)) > $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)
+    );
+  }
+
+  if (!empty($op)) {
+    $params[$fieldName][$op] = $fieldValue;
+  }
+  else {
+    $params[$fieldName] = $fieldValue;
   }
 }
 
 /**
  * Validate & swap out any pseudoconstants / options
  *
- * @param $params: api parameters
- * @param $entity: api entity name
- * @param $fieldName: field name used in api call (not necessarily the canonical name)
- * @param $fieldInfo: getfields meta-data
+ * @param mixed $fieldValue
+ * @param string $entity : api entity name
+ * @param string $fieldName : field name used in api call (not necessarily the canonical name)
+ * @param array $fieldInfo : getfields meta-data
+ * @throws \API_Exception
  */
-function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $fieldInfo) {
+function _civicrm_api3_api_match_pseudoconstant(&$fieldValue, $entity, $fieldName, $fieldInfo) {
   $options = CRM_Utils_Array::value('options', $fieldInfo);
+
   if (!$options) {
-    if(strtolower($entity) == 'profile' && !empty($fieldInfo['entity'])) {
+    if (strtolower($entity) == 'profile' && !empty($fieldInfo['entity'])) {
       // we need to get the options from the entity the field relates to
       $entity = $fieldInfo['entity'];
     }
@@ -1840,13 +1991,13 @@ function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $
 
   // If passed a value-separated string, explode to an array, then re-implode after matching values
   $implode = FALSE;
-  if (is_string($params[$fieldName]) && strpos($params[$fieldName], CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) {
-    $params[$fieldName] = CRM_Utils_Array::explodePadded($params[$fieldName]);
+  if (is_string($fieldValue) && strpos($fieldValue, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) {
+    $fieldValue = CRM_Utils_Array::explodePadded($fieldValue);
     $implode = TRUE;
   }
   // If passed multiple options, validate each
-  if (is_array($params[$fieldName])) {
-    foreach ($params[$fieldName] as &$value) {
+  if (is_array($fieldValue)) {
+    foreach ($fieldValue as &$value) {
       if (!is_array($value)) {
         _civicrm_api3_api_match_pseudoconstant_value($value, $options, $fieldName);
       }
@@ -1855,25 +2006,26 @@ function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $
     // need to verify that this is safe and doesn't break anything though.
     // Better yet would be to leave it as an array and ensure that every dao/bao can handle array input
     if ($implode) {
-      CRM_Utils_Array::implodePadded($params[$fieldName]);
+      CRM_Utils_Array::implodePadded($fieldValue);
     }
   }
   else {
-    _civicrm_api3_api_match_pseudoconstant_value($params[$fieldName], $options, $fieldName);
+    _civicrm_api3_api_match_pseudoconstant_value($fieldValue, $options, $fieldName);
   }
 }
 
 /**
  * Validate & swap a single option value for a field
  *
- * @param $value: field value
- * @param $options: array of options for this field
- * @param $fieldName: field name used in api call (not necessarily the canonical name)
+ * @param string $value field value
+ * @param array $options array of options for this field
+ * @param string $fieldName field name used in api call (not necessarily the canonical name)
  * @throws API_Exception
  */
 function _civicrm_api3_api_match_pseudoconstant_value(&$value, $options, $fieldName) {
   // If option is a key, no need to translate
-  if (array_key_exists($value, $options)) {
+  // or if no options are avaiable for pseudoconstant 'table' property
+  if (array_key_exists($value, $options) || !$options) {
     return;
   }
 
@@ -1895,10 +2047,14 @@ function _civicrm_api3_api_match_pseudoconstant_value(&$value, $options, $fieldN
 
 /**
  * Returns the canonical name of a field
- * @param $entity: api entity name (string should already be standardized - no camelCase)
- * @param $fieldName: any variation of a field's name (name, unique_name, api.alias)
  *
- * @return (string|bool) fieldName or FALSE if the field does not exist
+ * @param $entity
+ *   api entity name (string should already be standardized - no camelCase).
+ * @param $fieldName
+ *   any variation of a field's name (name, unique_name, api.alias).
+ *
+ * @return bool|string
+ *   fieldName or FALSE if the field does not exist
  */
 function _civicrm_api3_api_resolve_alias($entity, $fieldName) {
   if (strpos($fieldName, 'custom_') === 0 && is_numeric($fieldName[7])) {
@@ -1928,3 +2084,41 @@ function _civicrm_api3_api_resolve_alias($entity, $fieldName) {
   }
   return FALSE;
 }
+
+/**
+ * @param string $entity
+ * @param array $result
+ * @return string|array|null
+ */
+function _civicrm_api3_deprecation_check($entity, $result = array()) {
+  if ($entity) {
+    $apiFile = 'api/v3/' . _civicrm_api_get_camel_name($entity) . '.php';
+    if (CRM_Utils_File::isIncludable($apiFile)) {
+      require_once $apiFile;
+    }
+    $entity = _civicrm_api_get_entity_name_from_camel($entity);
+    $fnName = "_civicrm_api3_{$entity}_deprecation";
+    if (function_exists($fnName)) {
+      return $fnName($result);
+    }
+  }
+}
+
+/**
+ * In some case $params[$fieldName] holds Array value in this format Array([operator] => [value])
+ * So this function returns the actual field value
+ *
+ * @param array $params
+ * @param string $fieldName
+ * @return mixed
+ */
+function _civicrm_api3_field_value_check(&$params, $fieldName) {
+  $fieldValue = CRM_Utils_Array::value($fieldName, $params);
+  $op = NULL;
+
+  if (!empty($fieldValue) && is_array($fieldValue) && array_search(key($fieldValue), CRM_Core_DAO::acceptedSQLOperators())) {
+    $op = key($fieldValue);
+    $fieldValue = CRM_Utils_Array::value($op, $fieldValue);
+  }
+  return array($fieldValue, $op);
+}