X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=api%2Fv3%2Futils.php;h=f810fcbf42505af0f47c9e5b031f98fe701b0ada;hb=5ba3bfc895f1350346533fd6f0c0087a59dea7ee;hp=a0798270f4ff078a509569cd995da650ece75ae5;hpb=effb666add21105f719f9ada8e7ccd2621aada8b;p=civicrm-core.git diff --git a/api/v3/utils.php b/api/v3/utils.php index a0798270f4..f810fcbf42 100644 --- a/api/v3/utils.php +++ b/api/v3/utils.php @@ -50,11 +50,11 @@ function _civicrm_api3_initialize() { * * @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 options. One of the options is required + * @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 - * @ - * */ function civicrm_api3_verify_one_mandatory($params, $daoName = NULL, $keyoptions = array( )) { @@ -73,6 +73,7 @@ function civicrm_api3_verify_one_mandatory($params, $daoName = NULL, $keyoptions * @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 @@ -115,7 +116,9 @@ function civicrm_api3_verify_mandatory($params, $daoName = NULL, $keys = array( } } else { - if (!array_key_exists($key, $params) || empty($params[$key])) { + // Disallow empty values except for the number zero. + // TODO: create a utility for this since it's needed in many places + if (!array_key_exists($key, $params) || (empty($params[$key]) && $params[$key] !== 0 && $params[$key] !== '0')) { $unmatched[] = $key; } } @@ -127,11 +130,12 @@ function civicrm_api3_verify_mandatory($params, $daoName = NULL, $keys = array( /** * - * @param $msg * @param $data + * @param array $data * @param object $dao DAO / BAO object to be freed here * - * @return + * @throws API_Exception + * @return array */ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) { //fix me - $dao should be param 4 & 3 should be $apiRequest @@ -142,7 +146,8 @@ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) { if (is_array($dao)) { if ($msg == 'DB Error: constraint violation' || substr($msg, 0,9) == 'DB Error:' || $msg == 'DB Error: already exists') { try { - _civicrm_api3_validate_fields($dao['entity'], $dao['action'], $dao['params'], TRUE); + $fields = _civicrm_api3_api_getfields($dao); + _civicrm_api3_validate_fields($dao['entity'], $dao['action'], $dao['params'], $fields, TRUE); } catch(Exception $e) { $msg = $e->getMessage(); @@ -266,9 +271,10 @@ function _civicrm_api3_load_DAO($entity) { /** * Function to return the DAO of the function or Entity - * @param $name is either a function of the api (civicrm_{entity}_create or the entity name + * @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) { if (strpos($name, 'civicrm_api3') !== FALSE) { @@ -296,14 +302,20 @@ function _civicrm_api3_get_DAO($name) { /** * Function to return the DAO of the function or Entity - * @param $name is either a function of the api (civicrm_{entity}_create or the entity name + * @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) { $dao = _civicrm_api3_get_DAO($name); - $dao = str_replace("DAO", "BAO", $dao); - return $dao; + if (!$dao) { + return NULL; + } + $bao = str_replace("DAO", "BAO", $dao); + $file = strtr($bao, '_', '/') . '.php'; + // Check if this entity actually has a BAO. Fall back on the DAO if not. + return stream_resolve_include_path($file) ? $bao : $dao; } /** @@ -363,6 +375,7 @@ function _civicrm_api3_store_values(&$fields, &$params, &$values) { } return $valueFound; } + /** * The API supports 2 types of get requestion. The more complex uses the BAO query object. * This is a generic function for those functions that call it @@ -371,9 +384,13 @@ 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 * + * @param $entity * @param array $params as passed into api get or getcount function - * @param array $options array of options (so we can modify the filter) + * @param array $additional_options * @param bool $getCount are we just after the count + * + * @return + * @internal param array $options array of options (so we can modify the filter) */ function _civicrm_api3_get_using_query_object($entity, $params, $additional_options = array(), $getCount = NULL){ @@ -425,7 +442,20 @@ function _civicrm_api3_get_using_query_object($entity, $params, $additional_opti } $newParams = CRM_Contact_BAO_Query::convertFormValues($inputParams); + foreach ($newParams as &$newParam) { + 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) { + $newParam[1] = key($newParam[2]); + $newParam[2] = $sqlFilter; + } + } + + } $skipPermissions = CRM_Utils_Array::value('check_permissions', $params)? 0 :1; + list($entities, $options) = CRM_Contact_BAO_Query::apiQuery( $newParams, $returnProperties, @@ -559,9 +589,14 @@ 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 string $entity + * @param string $action + * * @return array $options options extracted from params */ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $entity = '', $action = '') { @@ -598,8 +633,8 @@ 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 (CRM_Utils_Array::value('id',$returnProperties)) { $returnProperties[$entity . '_id'] = 1; unset($returnProperties['id']); } @@ -611,15 +646,18 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $ } } - $options = array( - 'offset' => $offset, - 'sort' => $sort, - 'limit' => $limit, + 'offset' => CRM_Utils_Rule::integer($offset) ? $offset : NULL, + '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, ); + if ($options['sort'] && stristr($options['sort'], 'SELECT')) { + throw new API_Exception('invalid string in sort options'); + } + if (!$queryObject) { return $options; } @@ -634,12 +672,15 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $ if (substr($n, 0, 7) == 'return.') { $legacyreturnProperties[substr($n, 7)] = $v; } - elseif($n == 'id'){ + elseif ($n == 'id') { $inputParams[$entity. '_id'] = $v; } elseif (in_array($n, $otherVars)) {} - else{ + else { $inputParams[$n] = $v; + if ($v && !is_array($v) && stristr($v, 'SELECT')) { + throw new API_Exception('invalid string'); + } } } $options['return'] = array_merge($returnProperties, $legacyreturnProperties); @@ -649,8 +690,10 @@ 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 $entity */ function _civicrm_api3_apply_options_to_dao(&$params, &$dao, $entity) { @@ -699,8 +742,15 @@ 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 + * + * @return array + * * @params array of arrays (key = id) of array of fields + * * @static void * @access public */ @@ -751,9 +801,9 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE, /** * Converts an object to an array * - * @param object $dao (reference) object to convert - * @param array $values (reference) array - * @param array $uniqueFields + * @param object $dao (reference) object to convert + * @param array $values (reference) array + * @param array|bool $uniqueFields * * @return array * @static void @@ -805,8 +855,11 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $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 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) @@ -864,8 +917,9 @@ function _civicrm_api3_check_required_fields($params, $daoName, $return = FALSE) * @param $entity string API entity being accessed * @param $action string API action being performed * @param $params array params of the API call - * @param $throw bool whether to throw exception instead of returning false + * @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) { @@ -882,16 +936,20 @@ function _civicrm_api3_api_check_permission($entity, $action, &$params, $throw = return TRUE; } - foreach ($permissions as $perm) { - if (!CRM_Core_Permission::check($perm)) { - if ($throw) { - throw new Exception("API permission check failed for $entity/$action call; missing permission: $perm."); - } - else { - return FALSE; + 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; } @@ -901,6 +959,9 @@ function _civicrm_api3_api_check_permission($entity, $action, &$params, $throw = * @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 */ function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $entity = "") { $bao = new $bao_name(); @@ -918,6 +979,7 @@ function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $ * @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 + * @return array */ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) { @@ -945,6 +1007,16 @@ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) { if (is_null($bao)) { return civicrm_api3_create_error('Entity not created (' . $fct_name . ')'); } + elseif (is_a($bao, 'CRM_Core_Error')) { + //some wierd circular thing means the error takes itself as an argument + $msg = $bao->getMessages($bao); + // the api deals with entities on a one-by-one basis. However, the contribution bao pushes entities + // onto the error object - presumably because the contribution import is not handling multiple errors correctly + // so we need to reset the error object here to avoid getting concatenated errors + //@todo - the mulitple error handling should be moved out of the contribution object to the import / multiple entity processes + CRM_Core_Error::singleton()->reset(); + throw new API_Exception($msg); + } else { $values = array(); _civicrm_api3_object_to_array($bao, $values[$bao->id]); @@ -955,11 +1027,13 @@ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) { /** * For BAO's which don't have a create() or add() functions, use this fallback implementation. * - * FIXME There's an intuitive sense that this behavior should be defined somehow in the BAO/DAO class + * @fixme There's an intuitive sense that this behavior should be defined somehow in the BAO/DAO class * structure. In practice, that requires a fair amount of refactoring and/or kludgery. * * @param string $bao_name * @param array $params + * + * @throws API_Exception * @return CRM_Core_DAO|NULL an instance of the BAO */ function _civicrm_api3_basic_create_fallback($bao_name, &$params) { @@ -1018,10 +1092,10 @@ function _civicrm_api3_basic_delete($bao_name, &$params) { * * @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 - * */ function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $groupID = NULL, $subType = NULL, $subName = NULL) { $groupTree = &CRM_Core_BAO_CustomGroup::getTree($entity, @@ -1061,16 +1135,12 @@ function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $grou * @param string $entity * @param string $action * @param array $params - - * all variables are the same as per civicrm_api + * @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, $errorMode = NULL) { - //skip any entities without working getfields functions - $skippedEntities = array('entity', 'mailinggroup', 'customvalue', 'custom_value', 'mailing_group'); - if (in_array(strtolower($entity), $skippedEntities) || strtolower($action) == 'getfields') { - return; - } - $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => $action)); - $fields = array_intersect_key($fields['values'], $params); +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)) { case CRM_Utils_Type::T_INT: @@ -1128,7 +1198,9 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $errorMode = * * @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 $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? @@ -1153,7 +1225,9 @@ function _civicrm_api3_validate_date(&$params, &$fieldName, &$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 + * @param $fieldInfo + * @throws Exception + * @internal param array $fieldinfo array of fields from getfields function */ function _civicrm_api3_validate_constraint(&$params, &$fieldName, &$fieldInfo) { $dao = new $fieldInfo['FKClassName']; @@ -1170,7 +1244,9 @@ function _civicrm_api3_validate_constraint(&$params, &$fieldName, &$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 + * @param $fieldInfo + * @throws Exception + * @internal param array $fieldinfo array of fields from getfields function */ function _civicrm_api3_validate_uniquekey(&$params, &$fieldName, &$fieldInfo) { $existing = civicrm_api($params['entity'], 'get', array( @@ -1197,6 +1273,7 @@ function _civicrm_api3_validate_uniquekey(&$params, &$fieldName, &$fieldInfo) { * @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 */ function _civicrm_api3_generic_replace($entity, $params) { @@ -1207,9 +1284,7 @@ function _civicrm_api3_generic_replace($entity, $params) { } // Extract the keys -- somewhat scary, don't think too hard about it - $baseParams = $params; - unset($baseParams['values']); - unset($baseParams['sequential']); + $baseParams = _civicrm_api3_generic_replace_base_params($params); // Lookup pre-existing records $preexisting = civicrm_api($entity, 'get', $baseParams, $params); @@ -1262,10 +1337,27 @@ function _civicrm_api3_generic_replace($entity, $params) { } } +/** + * @param $params + * + * @return mixed + */ +function _civicrm_api3_generic_replace_base_params($params) { + $baseParams = $params; + unset($baseParams['values']); + unset($baseParams['sequential']); + unset($baseParams['options']); + return $baseParams; +} + /** * returns fields allowable by api + * * @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( )) { @@ -1310,6 +1402,7 @@ 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' $entity = CRM_Utils_Array::value('contact_type', $params); } $retrieveOnlyParent = FALSE; @@ -1360,24 +1453,17 @@ function _getStandardTypeFromCustomDataType($dataType) { '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) { +function _civicrm_api3_getdefaults($apiRequest, $fields) { $defaults = array(); - $result = civicrm_api($apiRequest['entity'], - 'getfields', - array( - 'version' => 3, - 'action' => $apiRequest['action'], - ) - ); - - foreach ($result['values'] as $field => $values) { + foreach ($fields as $field => $values) { if (isset($values['api.default'])) { $defaults[$field] = $values['api.default']; } @@ -1388,17 +1474,10 @@ function _civicrm_api3_getdefaults($apiRequest) { /** * Return array of defaults for the given API (function is a wrapper on getfields) */ -function _civicrm_api3_getrequired($apiRequest) { +function _civicrm_api3_getrequired($apiRequest, $fields) { $required = array('version'); - $result = civicrm_api($apiRequest['entity'], - 'getfields', - array( - 'version' => 3, - 'action' => $apiRequest['action'], - ) - ); - foreach ($result['values'] as $field => $values) { + foreach ($fields as $field => $values) { if (CRM_Utils_Array::value('api.required', $values)) { $required[] = $field; } @@ -1412,23 +1491,8 @@ function _civicrm_api3_getrequired($apiRequest) { * * Function also swaps unique fields for non-unique fields & vice versa. */ -function _civicrm_api3_swap_out_aliases(&$apiRequest) { - if (strtolower($apiRequest['action'] == 'getfields')) { - if (CRM_Utils_Array::value('api_action', $apiRequest['params'])) { - $apiRequest['params']['action'] = $apiRequest['params']['api_action']; - unset($apiRequest['params']['api_action']); - } - return; - } - $result = civicrm_api($apiRequest['entity'], - 'getfields', - array( - 'version' => 3, - 'action' => $apiRequest['action'], - ) - ); - - foreach ($result['values'] as $field => $values) { +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 aliased field is not set we try to use field alias @@ -1470,7 +1534,10 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest) { * * @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 $fieldInfo + * @param $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 @@ -1539,6 +1606,14 @@ function _civicrm_api3_resolve_contactID($contactIdExpr) { return NULL; } +/** + * Validate html (check for scripting attack) + * @param $params + * @param $fieldName + * @param $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)) { @@ -1551,7 +1626,11 @@ 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 array $fieldinfo array of fields from getfields function + * @param $fieldInfo + * @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 @@ -1596,11 +1675,15 @@ function _civicrm_api3_validate_string(&$params, &$fieldName, &$fieldInfo, $enti function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $fieldInfo) { $options = CRM_Utils_Array::value('options', $fieldInfo); if (!$options) { + if(strtolower($entity) == 'profile' && !empty($fieldInfo['entity'])) { + // we need to get the options from the entity the field relates to + $entity = $fieldInfo['entity']; + } $options = civicrm_api($entity, 'getoptions', array('version' => 3, 'field' => $fieldInfo['name'], 'context' => 'validate')); $options = CRM_Utils_Array::value('values', $options, array()); } - // If passed a value-seperated string, explode to an array, then re-implode after matching values + // 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]); @@ -1631,6 +1714,7 @@ function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $ * @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) + * @throws API_Exception */ function _civicrm_api3_api_match_pseudoconstant_value(&$value, $options, $fieldName) { // If option is a key, no need to translate