Merge pull request #1510 from ravishnair/CRM-13250
[civicrm-core.git] / api / v3 / utils.php
index cd67feb0b886332a3b162dc8bcc0c41b025e607e..14f6daa352ffbea6d20a59df98bc6410040b507a 100644 (file)
@@ -1,9 +1,7 @@
 <?php
-// $Id$
-
 /*
   +--------------------------------------------------------------------+
-  | CiviCRM version 4.3                                                |
+  | CiviCRM version 4.4                                                |
   +--------------------------------------------------------------------+
   | Copyright CiviCRM LLC (c) 2004-2013                                |
   +--------------------------------------------------------------------+
@@ -153,6 +151,11 @@ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) {
   }
   $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'];
+  }
   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);
@@ -184,8 +187,13 @@ function civicrm_api3_create_success($values = 1, $params = array(
         $values[$key]['id'] = $item[$entity . "_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'])){
+        // 4.4 legacy handling
+        $values[$key]['next_sched_contribution'] = $item['next_sched_contribution_date'];
+      }
     }
   }
 
@@ -205,7 +213,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'));
+    $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'));
     if ($undefined) {
       $result['undefined_fields'] = array_merge($undefined);
     }
@@ -216,7 +224,7 @@ function civicrm_api3_create_success($values = 1, $params = array(
 
   $result['version'] = 3;
   if (is_array($values)) {
-    $result['count'] = count($values);
+    $result['count'] = (int) count($values);
 
     // Convert value-separated strings to array
     _civicrm_api3_separate_values($values);
@@ -481,45 +489,9 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
     if (is_array($params[$field])) {
       //get the actual fieldname from db
       $fieldName = $allfields[$field]['name'];
-      //array is the syntax for SQL clause
-      foreach ($params[$field] as $operator => $criteria) {
-        if (in_array($operator, $acceptedSQLOperators)) {
-          switch ($operator) {
-            // unary operators
-
-            case 'IS NULL':
-            case 'IS NOT NULL':
-              $dao->whereAdd(sprintf('%s %s', $fieldName, $operator));
-              break;
-
-            // ternary operators
-
-            case 'BETWEEN':
-            case 'NOT BETWEEN':
-              if (empty($criteria[0]) || empty($criteria[1])) {
-                throw new exception("invalid criteria for $operator");
-              }
-              $dao->whereAdd(sprintf('%s ' . $operator . ' "%s" AND "%s"', $fieldName, CRM_Core_DAO::escapeString($criteria[0]), CRM_Core_DAO::escapeString($criteria[1])));
-              break;
-
-            // n-ary operators
-
-            case 'IN':
-            case 'NOT IN':
-              if (empty($criteria)) {
-                throw new exception("invalid criteria for $operator");
-              }
-              $escapedCriteria = array_map(array('CRM_Core_DAO', 'escapeString'), $criteria);
-              $dao->whereAdd(sprintf('%s %s ("%s")', $fieldName, $operator, implode('", "', $escapedCriteria)));
-              break;
-
-            // binary operators
-
-            default:
-
-              $dao->whereAdd(sprintf('%s %s "%s"', $fieldName, $operator, CRM_Core_DAO::escapeString($criteria)));
-          }
-        }
+      $where = CRM_Core_DAO::createSqlFilter($fieldName, $params[$field], 'String');
+      if(!empty($where)) {
+        $dao->whereAdd($where);
       }
     }
     else {
@@ -535,7 +507,7 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) {
       }
     }
   }
-  if (!empty($options['return']) && is_array($options['return'])) {
+  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);
@@ -593,6 +565,7 @@ function _civicrm_api3_apply_filters_to_dao($filterField, $filterValue, &$dao) {
  * @return array $options options extracted from params
  */
 function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $entity = '', $action = '') {
+  $is_count = FALSE;
   $sort = CRM_Utils_Array::value('sort', $params, 0);
   $sort = CRM_Utils_Array::value('option.sort', $params, $sort);
   $sort = CRM_Utils_Array::value('option_sort', $params, $sort);
@@ -607,6 +580,8 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
   $limit = CRM_Utils_Array::value('option_limit', $params, $limit);
 
   if (is_array(CRM_Utils_Array::value('options', $params))) {
+    // is count is set by generic getcount not user
+    $is_count = CRM_Utils_Array::value('is_count', $params['options']);
     $offset = CRM_Utils_Array::value('offset', $params['options'], $offset);
     $limit  = CRM_Utils_Array::value('limit', $params['options'], $limit);
     $sort   = CRM_Utils_Array::value('sort', $params['options'], $sort);
@@ -641,8 +616,10 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
     'offset' => $offset,
     'sort' => $sort,
     'limit' => $limit,
+    'is_count' => $is_count,
     'return' => !empty($returnProperties) ? $returnProperties : NULL,
   );
+
   if (!$queryObject) {
     return $options;
   }
@@ -678,9 +655,11 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
 function _civicrm_api3_apply_options_to_dao(&$params, &$dao, $entity) {
 
   $options = _civicrm_api3_get_options_from_params($params,FALSE,$entity);
-  $dao->limit((int)$options['offset'], (int)$options['limit']);
-  if (!empty($options['sort'])) {
-    $dao->orderBy($options['sort']);
+  if(!$options['is_count']) {
+    $dao->limit((int)$options['offset'], (int)$options['limit']);
+    if (!empty($options['sort'])) {
+      $dao->orderBy($options['sort']);
+    }
   }
 }
 
@@ -727,10 +706,16 @@ function _civicrm_api3_get_unique_name_array(&$bao) {
  */
 function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE, $entity = "") {
   $result = array();
+  if(isset($params['options']) && CRM_Utils_Array::value('is_count', $params['options'])) {
+    return $dao->count();
+  }
   if (empty($dao) || !$dao->find()) {
     return array();
   }
 
+  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) {
@@ -921,7 +906,7 @@ 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);
+      return civicrm_api3_create_success(_civicrm_api3_dao_to_array($bao, $params, FALSE, $entity), $params, $entity);
   }
   else {
     return _civicrm_api3_dao_to_array($bao, $params, FALSE, $entity);
@@ -963,7 +948,7 @@ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) {
   else {
     $values = array();
     _civicrm_api3_object_to_array($bao, $values[$bao->id]);
-    return civicrm_api3_create_success($values, $params, NULL, 'create', $bao);
+    return civicrm_api3_create_success($values, $params, $entity, 'create', $bao);
   }
 }
 
@@ -1008,7 +993,7 @@ function _civicrm_api3_basic_delete($bao_name, &$params) {
     if ($bao !== FALSE) {
       return civicrm_api3_create_success(TRUE);
     }
-    return civicrm_api3_create_error('Could not delete entity id ' . $params['id']);
+    throw new API_Exception('Could not delete entity id ' . $params['id']);
   }
   elseif (method_exists($bao_name, 'delete')) {
     $dao = new $bao_name();
@@ -1020,11 +1005,11 @@ function _civicrm_api3_basic_delete($bao_name, &$params) {
       }
     }
     else {
-      return civicrm_api3_create_error('Could not delete entity id ' . $params['id']);
+      throw new API_Exception('Could not delete entity id ' . $params['id']);
     }
   }
 
-  return civicrm_api3_create_error('no delete method found');
+  throw new API_Exception('no delete method found');
 }
 
 /**
@@ -1108,7 +1093,7 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $errorMode =
         break;
 
       case CRM_Utils_Type::T_MONEY:
-        if (!CRM_Utils_Rule::money($params[$fieldName])) {
+        if (!CRM_Utils_Rule::money($params[$fieldName]) && !empty($params[$fieldName])) {
           throw new Exception($fieldName . " is  not a valid amount: " . $params[$fieldName]);
         }
     }
@@ -1350,6 +1335,7 @@ function _civicrm_api_get_custom_fields($entity, &$params) {
   foreach ($customfields as $key => $value) {
     // Regular fields have a 'name' property
     $value['name'] = 'custom_' . $key;
+    $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);
@@ -1358,17 +1344,35 @@ function _civicrm_api_get_custom_fields($entity, &$params) {
   }
   return $customfields;
 }
-
+/**
+ * Translate the custom field data_type attribute into a std 'type'
+ */
+function _getStandardTypeFromCustomDataType($dataType) {
+  $mapping = array(
+    'String' => CRM_Utils_Type::T_STRING,
+    'Int' => CRM_Utils_Type::T_INT,
+    'Money' => CRM_Utils_Type::T_MONEY,
+    'Memo' => CRM_Utils_Type::T_LONGTEXT,
+    'Float' => CRM_Utils_Type::T_FLOAT,
+    'Date' => CRM_Utils_Type::T_DATE,
+    'Boolean' => CRM_Utils_Type::T_BOOLEAN,
+    'StateProvince' => CRM_Utils_Type::T_INT,
+    'File' => CRM_Utils_Type::T_STRING,
+    'Link' => CRM_Utils_Type::T_STRING,
+    'ContactReference' => CRM_Utils_Type::T_INT,
+    'Country' => CRM_Utils_Type::T_INT,
+  );
+  return $mapping[$dataType];
+}
 /**
  * Return array of defaults for the given API (function is a wrapper on getfields)
  */
 function _civicrm_api3_getdefaults($apiRequest) {
   $defaults = array();
 
-  $result = civicrm_api($apiRequest['entity'],
+  $result = civicrm_api3($apiRequest['entity'],
     'getfields',
     array(
-      'version' => 3,
       'action' => $apiRequest['action'],
     )
   );
@@ -1416,10 +1420,9 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest) {
     }
     return;
   }
-  $result = civicrm_api($apiRequest['entity'],
+  $result = civicrm_api3($apiRequest['entity'],
     'getfields',
     array(
-      'version' => 3,
       'action' => $apiRequest['action'],
     )
   );
@@ -1471,10 +1474,14 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest) {
 function _civicrm_api3_validate_integer(&$params, &$fieldName, &$fieldInfo, $entity) {
   //if fieldname exists in params
   if (CRM_Utils_Array::value($fieldName, $params)) {
-    //if value = 'user_contact_id' replace value with logged in user id
-    if ($params[$fieldName] == "user_contact_id") {
-      $session = &CRM_Core_Session::singleton();
-      $params[$fieldName] = $session->get('userID');
+    // 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 ('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;
+      }
     }
     if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options'])) {
       _civicrm_api3_api_match_pseudoconstant($params, $entity, $fieldName, $fieldInfo);
@@ -1499,10 +1506,42 @@ function _civicrm_api3_validate_integer(&$params, &$fieldName, &$fieldInfo, $ent
   }
 }
 
+/**
+ * Determine a contact ID using a string expression
+ *
+ * @param string $contactIdExpr e.g. "user_contact_id" or "@user:username"
+ * @return int|NULL|'unknown-user'
+ */
+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)) {
+    $config = CRM_Core_Config::singleton();
+
+    $ufID = $config->userSystem->getUfId($matches[1]);
+    if (!$ufID) {
+      return 'unknown-user';
+    }
+
+    $contactID = CRM_Core_BAO_UFMatch::getContactId($ufID);
+    if (!$contactID) {
+      return 'unknown-user';
+    }
+
+    return $contactID;
+  }
+  return NULL;
+}
+
 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"));
+      throw new API_Exception('Illegal characters in input (potential scripting attack)', array("field"=>$fieldName,"error_code"=>"xss"));
     }
   }
 }
@@ -1533,7 +1572,7 @@ function _civicrm_api3_validate_string(&$params, &$fieldName, &$fieldInfo, $enti
         throw new Exception("Currency not a valid code: $value");
       }
     }
-    if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options'])) {
+    if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options']) || !empty($fieldInfo['enumValues'])) {
       _civicrm_api3_api_match_pseudoconstant($params, $entity, $fieldName, $fieldInfo);
     }
     // Check our field length
@@ -1633,6 +1672,9 @@ function _civicrm_api3_api_resolve_alias($entity, $fieldName) {
     'action' => 'create',
   ));
   $meta = $result['values'];
+  if (!isset($meta[$fieldName]['name']) && isset($meta[$fieldName . '_id'])) {
+    $fieldName = $fieldName . '_id';
+  }
   if (isset($meta[$fieldName])) {
     return $meta[$fieldName]['name'];
   }