Merge pull request #3433 from davecivicrm/CRM-14798
[civicrm-core.git] / api / v3 / utils.php
index 781c36d8924917cc8fe1af79f549fc7257522d67..1ee834087b9e59ff4d970db954dedc249fb7d344 100644 (file)
@@ -151,7 +151,7 @@ function civicrm_api3_create_error($msg, $data = array()) {
 /**
  * Format array in result output styple
  *
- * @param array $values values generated by API operation (the result)
+ * @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
@@ -234,7 +234,7 @@ function civicrm_api3_create_success($values = 1, $params = array(), $entity = N
   }
   if(!empty($params['options']['metadata'])) {
     // we've made metadata an array but only supporting 'fields' atm
-    if(in_array('fields', $params['options']['metadata'])) {
+    if(in_array('fields', (array) $params['options']['metadata'])) {
       $fields = civicrm_api3($entity, 'getfields', array('action' => substr($action, 0, 3) == 'get' ? 'get' : 'create'));
       $result['metadata']['fields'] = $fields['values'];
     }
@@ -538,6 +538,13 @@ 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);
@@ -657,6 +664,7 @@ function _civicrm_api3_apply_filters_to_dao($filterField, $filterValue, &$dao) {
  * @param string $entity
  * @param string $action
  *
+ * @throws API_Exception
  * @return array $options options extracted from params
  */
 function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $entity = '', $action = '') {
@@ -711,7 +719,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')) {
@@ -792,6 +800,9 @@ 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_BAO $bao
+ *
+ * @return mixed
  */
 function _civicrm_api3_get_unique_name_array(&$bao) {
   $fields = $bao->fields();
@@ -804,11 +815,13 @@ function _civicrm_api3_get_unique_name_array(&$bao) {
 /**
  * Converts an DAO object to an array
  *
- * @param  object $dao           (reference )object to convert
+ * @param  object $dao (reference )object to convert
  * @param null $params
  * @param bool $uniqueFields
  * @param string $entity
  *
+ * @param bool $autoFind
+ *
  * @return array
  *
  * @params array of arrays (key = id) of array of fields
@@ -831,15 +844,6 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE,
   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) && !empty($params['return']) && 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));
 
@@ -854,7 +858,8 @@ 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);
     }
   }
@@ -863,6 +868,27 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE,
   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 $params
+ * @param string $entity - entity name in CamelCase
+ *
+ * @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
  *
@@ -910,6 +936,22 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity
   }
 }
 
+/**
+ * @param $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);
+}
+
 /**
  * @deprecated
  * This function ensures that we have the right input parameters
@@ -993,19 +1035,22 @@ function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $
       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
+ *
+ * @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));
@@ -1084,6 +1129,12 @@ 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 $bao_name
+ * @param $params
+ *
+ * @return array API result array
+ * @throws API_Exception
  */
 function _civicrm_api3_basic_delete($bao_name, &$params) {
 
@@ -1125,7 +1176,7 @@ function _civicrm_api3_basic_delete($bao_name, &$params) {
  * @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,
@@ -1135,19 +1186,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');
       }
     }
   }
@@ -1177,6 +1232,7 @@ 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;
@@ -1225,27 +1281,45 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $fields, $err
  *
  * @param array $params params from civicrm_api
  * @param string $fieldName uniquename of field being checked
- * @param $fieldInfo
+ * @param array $fieldInfo
  * @throws Exception
- * @internal param array $fieldinfo array of fields from getfields function
  */
 function _civicrm_api3_validate_date(&$params, &$fieldName, &$fieldInfo) {
   //should we check first to prevent it from being copied if they have passed in sql friendly format?
   if (!empty($params[$fieldInfo['name']])) {
-    //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);
+    $params[$fieldInfo['name']] = _civicrm_api3_getValidDate($params[$fieldInfo['name']], $fieldInfo['name'], $fieldInfo['type']);
   }
   if ((CRM_Utils_Array::value('name', $fieldInfo) != $fieldName) && !empty($params[$fieldName])) {
-    //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]);
+    $params[$fieldName] = _civicrm_api3_getValidDate($params[$fieldName], $fieldName, $fieldInfo['type']);
+  }
+}
+
+/**
+ * convert date into BAO friendly date
+ * we accept 'whatever strtotime accepts'
+ *
+ * @param string $dateValue
+ * @param $fieldName
+ * @param $fieldType
+ *
+ * @throws Exception
+ * @internal param $fieldInfo
+ *
+ * @internal param $params
+ * @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);
 }
 
 /**
@@ -1533,7 +1607,6 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest, $fields) {
  * @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 (!empty($params[$fieldName])) {
     // if value = 'user_contact_id' (or similar), replace value with contact id
     if (!is_numeric($params[$fieldName]) && is_scalar($params[$fieldName])) {
@@ -1574,12 +1647,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]);
@@ -1617,11 +1687,10 @@ function _civicrm_api3_validate_html(&$params, &$fieldName, &$fieldInfo) {
  * 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 array $fieldInfo array of fields from getfields function
  * @param $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
@@ -1731,10 +1800,11 @@ 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 (string|bool) 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])) {