X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=api%2Fv3%2Futils.php;h=a9f0762464574c4725bc39a2ed55b0e7378a0e49;hb=7fbb4198d03adaf0b536941641b438103998efa3;hp=bc50e3cc28cae0f3a6760ba808e2091ec96c442c;hpb=af04e7c537043c3351109e66a2f38f209bfb8e53;p=civicrm-core.git diff --git a/api/v3/utils.php b/api/v3/utils.php index bc50e3cc28..a9f0762464 100644 --- a/api/v3/utils.php +++ b/api/v3/utils.php @@ -1,6 +1,4 @@ register(); + CRM_Core_Config::singleton(); +} /** * Wrapper Function for civicrm_verify_mandatory to make it simple to pass either / or fields for checking @@ -152,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); @@ -187,16 +191,8 @@ function civicrm_api3_create_success($values = 1, $params = array( } } } - //if ( array_key_exists ('debug',$params) && is_object ($dao)) { - if (is_array($params) && array_key_exists('debug', $params)) { - if (!is_object($dao)) { - $d = _civicrm_api3_get_DAO(CRM_Utils_Array::value('entity', $params)); - if (!empty($d)) { - $file = str_replace('_', '/', $d) . ".php"; - require_once ($file); - $dao = new $d(); - } - } + + if (is_array($params) && !empty($params['debug'])) { if (is_string($action) && $action != 'getfields') { $apiFields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => $action) + $params); } @@ -212,7 +208,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); } @@ -223,7 +219,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); @@ -259,8 +255,6 @@ function _civicrm_api3_load_DAO($entity) { if (empty($dao)) { return FALSE; } - $file = str_replace('_', '/', $dao) . ".php"; - require_once ($file); $d = new $dao(); return $d; } @@ -272,11 +266,6 @@ function _civicrm_api3_load_DAO($entity) { * eg. "civicrm_api3_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact" */ function _civicrm_api3_get_DAO($name) { - static $dao = NULL; - if (!$dao) { - require ('CRM/Core/DAO/listAll.php'); - } - if (strpos($name, 'civicrm_api3') !== FALSE) { $last = strrpos($name, '_'); // len ('civicrm_api3_') == 13 @@ -287,29 +276,17 @@ function _civicrm_api3_get_DAO($name) { $name = 'Contact'; } - //hack to deal with incorrectly named BAO/DAO - see CRM-10859 - remove after rename - if($name == 'price_set'){ - return 'CRM_Price_DAO_Set'; - } - if($name == 'price_field'){ - return 'CRM_Price_DAO_Field'; - } - if($name == 'price_field_value'){ - return 'CRM_Price_DAO_FieldValue'; - } - // these aren't listed on ticket CRM-10859 - but same problem - lack of standardisation - if($name == 'mailing_job' || $name == 'MailingJob'){ - return 'CRM_Mailing_BAO_Job'; - } + //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'; } if(strtolower($name) == 'im'){ return 'CRM_Core_BAO_IM'; } - - - return CRM_Utils_Array::value(_civicrm_api_get_camel_name($name, 3), $dao); + return CRM_Core_DAO_AllCoreTables::getFullName(_civicrm_api_get_camel_name($name, 3)); } /** @@ -346,7 +323,7 @@ function _civicrm_api3_separate_values(&$values) { } /** - * This is a wrapper for api_store_values which will check the suitable fields using getfields + * This is a legacy wrapper for api_store_values which will check the suitable fields using getfields * rather than DAO->fields * * Getfields has handling for how to deal with uniquenames which dao->fields doesn't @@ -475,6 +452,8 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) { //if entity_id is set then treat it as ID (will be overridden by id if set) $dao->id = $params[$entity . "_id"]; } + + $options = _civicrm_api3_get_options_from_params($params); //apply options like sort _civicrm_api3_apply_options_to_dao($params, $dao, $entity); @@ -548,32 +527,38 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $entity) { } else { if ($unique) { - $dao->$allfields[$field]['name'] = $params[$field]; + $daoFieldName = $allfields[$field]['name']; + if (empty($daoFieldName)) { + throw new API_Exception("Failed to determine field name for \"$field\""); + } + $dao->{$daoFieldName} = $params[$field]; } else { $dao->$field = $params[$field]; } } } - if (!empty($params['return']) && is_array($params['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); - $returnMatched = array_intersect($params['return'], $allfields); - $returnUniqueMatched = array_intersect( - array_diff(// not already matched on the field names - $params['return'], - $returnMatched), - array_flip($allfields)// but a match for the field keys - ); - + $returnMatched = array_intersect(array_keys($options['return']), $allfields); foreach ($returnMatched as $returnValue) { - $dao->selectAdd($returnValue); + $dao->selectAdd($returnValue); } + + $unmatchedFields = array_diff(// not already matched on the field names + array_keys($options['return']), + $returnMatched + ); + + $returnUniqueMatched = array_intersect( + $unmatchedFields, + array_flip($allfields)// but a match for the field keys + ); foreach ($returnUniqueMatched as $uniqueVal){ $dao->selectAdd($allfields[$uniqueVal]); - } - $dao->selectAdd('id'); } } @@ -611,6 +596,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); @@ -625,6 +611,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); @@ -659,8 +647,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; } @@ -696,9 +686,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']); + } } } @@ -745,10 +737,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) { @@ -820,7 +818,7 @@ function _civicrm_api3_custom_format_params($params, &$values, $extends, $entity $values['custom'] = array(); foreach ($params as $key => $value) { list($customFieldID, $customValueID) = CRM_Core_BAO_CustomField::getKeyID($key, TRUE); - if ($customFieldID) { + if ($customFieldID && (!IS_NULL($value))) { CRM_Core_BAO_CustomField::formatCustomField($customFieldID, $values['custom'], $value, $extends, $customValueID, $entityId, FALSE, FALSE ); @@ -907,8 +905,6 @@ function _civicrm_api3_api_check_permission($entity, $action, &$params, $throw = return TRUE; } - require_once 'CRM/Core/Permission.php'; - require_once 'CRM/Core/DAO/permissions.php'; $permissions = _civicrm_api3_permissions($entity, $action, $params); @@ -941,7 +937,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); @@ -957,22 +953,28 @@ function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $ function _civicrm_api3_basic_create($bao_name, &$params, $entity = NULL) { $args = array(&$params); - if(!empty($entity)){ + if (!empty($entity)) { $ids = array($entity => CRM_Utils_Array::value('id', $params)); $args[] = &$ids; } + if (method_exists($bao_name, 'create')) { $fct = 'create'; + $fct_name = $bao_name . '::' . $fct; + $bao = call_user_func_array(array($bao_name, $fct), $args); } elseif (method_exists($bao_name, 'add')) { $fct = 'add'; + $fct_name = $bao_name . '::' . $fct; + $bao = call_user_func_array(array($bao_name, $fct), $args); } - if (!isset($fct)) { - return civicrm_api3_create_error('Entity not created, missing create or add method for ' . $bao_name); + else { + $fct_name = '_civicrm_api3_basic_create_fallback'; + $bao = _civicrm_api3_basic_create_fallback($bao_name, $params); } - $bao = call_user_func_array(array($bao_name, $fct), $args); + if (is_null($bao)) { - return civicrm_api3_create_error('Entity not created ' . $bao_name . '::' . $fct); + return civicrm_api3_create_error('Entity not created (' . $fct_name . ')'); } else { $values = array(); @@ -981,6 +983,34 @@ 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 + * structure. In practice, that requires a fair amount of refactoring and/or kludgery. + * + * @param string $bao_name + * @param array $params + * @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)); + 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, + )); + } + $hook = empty($params['id']) ? 'create' : 'edit'; + + CRM_Utils_Hook::pre($hook, $entityName, CRM_Utils_Array::value('id', $params), $params); + $instance = new $bao_name(); + $instance->copyValues($params); + $instance->save(); + CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance); + + return $instance; +} + /** * 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 @@ -994,7 +1024,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(); @@ -1006,11 +1036,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'); } /** @@ -1025,8 +1055,6 @@ function _civicrm_api3_basic_delete($bao_name, &$params) { * */ function _civicrm_api3_custom_data_get(&$returnArray, $entity, $entity_id, $groupID = NULL, $subType = NULL, $subName = NULL) { - require_once 'CRM/Core/BAO/CustomGroup.php'; - require_once 'CRM/Core/BAO/CustomField.php'; $groupTree = &CRM_Core_BAO_CustomGroup::getTree($entity, CRM_Core_DAO::$_nullObject, $entity_id, @@ -1074,45 +1102,46 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $errorMode = } $fields = civicrm_api($entity, 'getfields', array('version' => 3, 'action' => $action)); $fields = array_intersect_key($fields['values'], $params); - foreach ($fields as $fieldname => $fieldInfo) { + foreach ($fields as $fieldName => $fieldInfo) { switch (CRM_Utils_Array::value('type', $fieldInfo)) { case CRM_Utils_Type::T_INT: //field is of type integer - _civicrm_api3_validate_integer($params, $fieldname, $fieldInfo, $entity); + _civicrm_api3_validate_integer($params, $fieldName, $fieldInfo, $entity); break; case 4: case 12: //field is of type date or datetime - _civicrm_api3_validate_date($params, $fieldname, $fieldInfo); + _civicrm_api3_validate_date($params, $fieldName, $fieldInfo); break; + case 32://blob - _civicrm_api3_validate_html($params, $fieldname, $fieldInfo); + _civicrm_api3_validate_html($params, $fieldName, $fieldInfo); break; - case CRM_Utils_Type::T_STRING: - _civicrm_api3_validate_string($params, $fieldname, $fieldInfo); + case CRM_Utils_Type::T_STRING: + _civicrm_api3_validate_string($params, $fieldName, $fieldInfo, $entity); break; case CRM_Utils_Type::T_MONEY: - if (!CRM_Utils_Rule::money($params[$fieldname])) { - throw new Exception($fieldname . " is not a valid amount: " . $params[$fieldname]); + if (!CRM_Utils_Rule::money($params[$fieldName])) { + throw new Exception($fieldName . " is not a valid amount: " . $params[$fieldName]); } } // 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)) { - _civicrm_api3_validate_constraint($params, $fieldname, $fieldInfo); + if (CRM_Utils_Array::value($fieldName, $params)) { + _civicrm_api3_validate_constraint($params, $fieldName, $fieldInfo); } elseif (CRM_Utils_Array::value('required', $fieldInfo)) { - throw new Exception("DB Constraint Violation - possibly $fieldname should possibly be marked as mandatory for this API. If so, please raise a bug report"); + 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)) { $params['entity'] = $entity; - _civicrm_api3_validate_uniquekey($params, $fieldname, $fieldInfo); + _civicrm_api3_validate_uniquekey($params, $fieldName, $fieldInfo); } } } @@ -1129,10 +1158,10 @@ function _civicrm_api3_validate_fields($entity, $action, &$params, $errorMode = * 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 string $fieldName uniquename of field being checked * @param array $fieldinfo array of fields from getfields function */ -function _civicrm_api3_validate_date(&$params, &$fieldname, &$fieldInfo) { +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 (CRM_Utils_Array::value($fieldInfo['name'], $params)) { //accept 'whatever strtotime accepts @@ -1141,12 +1170,12 @@ function _civicrm_api3_validate_date(&$params, &$fieldname, &$fieldInfo) { } $params[$fieldInfo['name']] = CRM_Utils_Date::processDate($params[$fieldInfo['name']]); } - if ((CRM_Utils_Array::value('name', $fieldInfo) != $fieldname) && CRM_Utils_Array::value($fieldname, $params)) { + 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]); + if (strtotime($params[$fieldName]) === FALSE) { + throw new Exception($fieldName . " is not a valid date: " . $params[$fieldName]); } - $params[$fieldname] = CRM_Utils_Date::processDate($params[$fieldname]); + $params[$fieldName] = CRM_Utils_Date::processDate($params[$fieldName]); } } @@ -1154,18 +1183,16 @@ function _civicrm_api3_validate_date(&$params, &$fieldname, &$fieldInfo) { * Validate foreign constraint fields being passed into API. * * @param array $params params from civicrm_api - * @param string $fieldname uniquename of field being checked + * @param string $fieldName uniquename of field being checked * @param array $fieldinfo array of fields from getfields function */ -function _civicrm_api3_validate_constraint(&$params, &$fieldname, &$fieldInfo) { - $file = str_replace('_', '/', $fieldInfo['FKClassName']) . ".php"; - require_once ($file); +function _civicrm_api3_validate_constraint(&$params, &$fieldName, &$fieldInfo) { $dao = new $fieldInfo['FKClassName']; - $dao->id = $params[$fieldname]; + $dao->id = $params[$fieldName]; $dao->selectAdd(); $dao->selectAdd('id'); if (!$dao->find()) { - throw new Exception("$fieldname is not valid : " . $params[$fieldname]); + throw new Exception("$fieldName is not valid : " . $params[$fieldName]); } } @@ -1173,18 +1200,18 @@ function _civicrm_api3_validate_constraint(&$params, &$fieldname, &$fieldInfo) { * Validate foreign constraint fields being passed into API. * * @param array $params params from civicrm_api - * @param string $fieldname uniquename of field being checked + * @param string $fieldName uniquename of field being checked * @param array $fieldinfo array of fields from getfields function */ -function _civicrm_api3_validate_uniquekey(&$params, &$fieldname, &$fieldInfo) { +function _civicrm_api3_validate_uniquekey(&$params, &$fieldName, &$fieldInfo) { $existing = civicrm_api($params['entity'], 'get', array( 'version' => $params['version'], - $fieldname => $params[$fieldname], + $fieldName => $params[$fieldName], )); // 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 Exception("Field: `$fieldName` must be unique. An conflicting entity already exists - id: " . $existing['id']); } } @@ -1204,7 +1231,6 @@ function _civicrm_api3_validate_uniquekey(&$params, &$fieldname, &$fieldInfo) { */ function _civicrm_api3_generic_replace($entity, $params) { - require_once 'CRM/Core/Transaction.php'; $transaction = new CRM_Core_Transaction(); try { if (!is_array($params['values'])) { @@ -1279,8 +1305,6 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array( if (empty($dao)) { return array(); } - $file = str_replace('_', '/', $dao) . ".php"; - require_once ($file); $d = new $dao(); $fields = $d->fields(); // replace uniqueNames by the normal names as the key @@ -1314,7 +1338,6 @@ function _civicrm_api_get_fields($entity, $unique = FALSE, &$params = array( * fields are prefixed with 'custom_' to represent api params */ function _civicrm_api_get_custom_fields($entity, &$params) { - require_once 'CRM/Core/BAO/CustomField.php'; $customfields = array(); $entity = _civicrm_api_get_camel_name($entity); if (strtolower($entity) == 'contact') { @@ -1341,10 +1364,12 @@ function _civicrm_api_get_custom_fields($entity, &$params) { } foreach ($customfields as $key => $value) { + // Regular fields have a 'name' property + $value['name'] = 'custom_' . $key; $customfields['custom_' . $key] = $value; - if(in_array('custom_' . $key, $getoptions)){ - $customfields['custom_' . $key]['options'] = CRM_Core_BAO_CustomOption::valuesByID($key); - } + if (in_array('custom_' . $key, $getoptions)) { + $customfields['custom_' . $key]['options'] = CRM_Core_BAO_CustomOption::valuesByID($key); + } unset($customfields[$key]); } return $customfields; @@ -1456,59 +1481,80 @@ function _civicrm_api3_swap_out_aliases(&$apiRequest) { * It currently converts the incoming value 'user_contact_id' into the id of the currenty logged in user * * @param array $params params from civicrm_api - * @param string $fieldname uniquename of field being checked + * @param string $fieldName uniquename of field being checked * @param array $fieldinfo array of fields from getfields function */ -function _civicrm_api3_validate_integer(&$params, &$fieldname, &$fieldInfo, $entity) { +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 (CRM_Utils_Array::value('pseudoconstant', $fieldInfo) ) { - $constant = CRM_Utils_Array::value('options', $fieldInfo); - if (is_numeric($params[$fieldname]) && !CRM_Utils_Array::value('FKClassName',$fieldInfo) && !array_key_exists($params[$fieldname], $fieldInfo['options'])) { - throw new API_Exception("$fieldname is not valid", 2001, array('error_field' => $fieldname,"type"=>"integer")); + if (CRM_Utils_Array::value($fieldName, $params)) { + // 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; } } - // we are looking for strings that should be swapped out e.g swap 'Donation' to financial_type_id 1 - if (!is_numeric($params[$fieldname]) && !is_array($params[$fieldname])) { - if(CRM_Utils_Array::value('FKClassName', $fieldInfo)){ - // we'll get the options for this now since we are doing a swap out - $options = civicrm_api($entity, 'getoptions', array('version' => 3, 'field' => $fieldname)); - if(empty($options['is_error'])){ - $fieldInfo['options'] = $options['values']; - } - } - if(!empty($fieldInfo['options'])){ - $numericvalue = array_search($params[$fieldname], $fieldInfo['options']); - if (empty($numericvalue)) { - throw new Exception("$fieldname " . $params[$fieldname] . " is not valid"); - } - else { - $params[$fieldname] = $numericvalue; - } + if (!empty($fieldInfo['pseudoconstant']) || !empty($fieldInfo['options'])) { + _civicrm_api3_api_match_pseudoconstant($params, $entity, $fieldName, $fieldInfo); + } + + // After swapping options, ensure we have an integer(s) + foreach ((array) ($params[$fieldName]) 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")); } } - // once we have done any swaps check our field length - if(is_string($params[$fieldname]) && + // Check our field length + if(is_string($params[$fieldName]) && CRM_Utils_Array::value('maxlength',$fieldInfo) - && strlen($params[$fieldname]) > $fieldInfo['maxlength'] + && 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']) + 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']) ); } } } -function _civicrm_api3_validate_html(&$params, &$fieldname, &$fieldInfo) { - if ($value = CRM_Utils_Array::value($fieldname, $params)) { +/** + * 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")); } } } @@ -1516,38 +1562,142 @@ 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 string $fieldName uniquename of field being checked * @param array $fieldinfo array of fields from getfields function */ -function _civicrm_api3_validate_string(&$params, &$fieldname, &$fieldInfo) { +function _civicrm_api3_validate_string(&$params, &$fieldName, &$fieldInfo, $entity) { // If fieldname exists in params - $value = (string) CRM_Utils_Array::value($fieldname, $params,''); + $value = CRM_Utils_Array::value($fieldName, $params, ''); + if(!is_array($value)){ + $value = (string) $value; + } + 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 ($fieldName == 'currency') { if (!CRM_Utils_Rule::currencyCode($value)) { throw new Exception("Currency not a valid code: $value"); } } - if (!empty ($fieldInfo['options'])) { - // Validate & swap out any pseudoconstants / options - $options = $fieldInfo['options']; - $lowerCaseOptions = array_map("strtolower", $options); - // If value passed is not a key, it may be a label - // Try to lookup key from label - if it can't be found throw error - if (!isset($options[strtolower($value)]) && !isset($options[$value]) ) { - if (!in_array(strtolower($value), $lowerCaseOptions)) { - throw new Exception("$fieldname `$value` is not valid."); - } - } + 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) + throw new API_Exception("Value for $fieldName is " . strlen($value) . " characters - This field has a maxlength of {$fieldInfo['maxlength']} characters.", + 2100, array('field' => $fieldName) ); } } } + +/** + * 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 + */ +function _civicrm_api3_api_match_pseudoconstant(&$params, $entity, $fieldName, $fieldInfo) { + $options = CRM_Utils_Array::value('options', $fieldInfo); + if (!$options) { + $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 + $implode = FALSE; + if (is_string($params[$fieldName]) && strpos($params[$fieldName], CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) { + $params[$fieldName] = CRM_Utils_Array::explodePadded($params[$fieldName]); + $implode = TRUE; + } + // If passed multiple options, validate each + if (is_array($params[$fieldName])) { + foreach ($params[$fieldName] as &$value) { + if (!is_array($value)) { + _civicrm_api3_api_match_pseudoconstant_value($value, $options, $fieldName); + } + } + // TODO: unwrap the call to implodePadded from the conditional and do it always + // 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]); + } + } + else { + _civicrm_api3_api_match_pseudoconstant_value($params[$fieldName], $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) + */ +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)) { + return; + } + + // Translate value into key + $newValue = array_search($value, $options); + if ($newValue !== FALSE) { + $value = $newValue; + return; + } + // Case-insensitive matching + $newValue = strtolower($value); + $options = array_map("strtolower", $options); + $newValue = array_search($newValue, $options); + if ($newValue === FALSE) { + throw new API_Exception("'$value' is not a valid option for field $fieldName", 2001, array('error_field' => $fieldName)); + } + $value = $newValue; +} + +/** + * 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 + */ +function _civicrm_api3_api_resolve_alias($entity, $fieldName) { + if (strpos($fieldName, 'custom_') === 0 && is_numeric($fieldName[7])) { + return $fieldName; + } + if ($fieldName == "{$entity}_id") { + return 'id'; + } + $result = civicrm_api($entity, 'getfields', array( + 'version' => 3, + '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']; + } + foreach ($meta as $info) { + if ($fieldName == CRM_Utils_Array::value('uniqueName', $info)) { + return $info['name']; + } + if (array_search($fieldName, CRM_Utils_Array::value('api.aliases', $info, array())) !== FALSE) { + return $info['name']; + } + } + return FALSE; +}