/** * Set defaults for api.getlist * * @param $entity string * @param $request array */ function _civicrm_api3_generic_getList_defaults($entity, &$request) { $config = CRM_Core_Config::singleton(); $fields = _civicrm_api_get_fields($entity); $defaults = array('page_num' => 1, 'input' => '', 'image_field' => NULL, 'id_field' => 'id', 'params' => array()); // Find main field from meta foreach (array('sort_name', 'title', 'label', 'name') as $field) { if (isset($fields[$field])) { $defaults['label_field'] = $defaults['search_field'] = $field; break; } } foreach (array('description') as $field) { if (isset($fields[$field])) { $defaults['description_field'] = $field; break; } } $resultsPerPage = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'search_autocomplete_count', NULL, 10); $request += $defaults; // Default api params $params = array('options' => array('limit' => $resultsPerPage + 1, 'offset' => ($request['page_num'] - 1) * $resultsPerPage, 'sort' => $request['label_field']), 'sequential' => 1); // When searching e.g. autocomplete if ($request['input']) { $params[$request['search_field']] = array('LIKE' => ($config->includeWildCardInName ? '%' : '') . $request['input'] . '%'); } // When looking up a field e.g. displaying existing record if (!empty($request['id'])) { if (is_string($request['id']) && strpos(',', $request['id'])) { $request['id'] = explode(',', $request['id']); } $params[$request['id_field']] = is_array($request['id']) ? array('IN' => $request['id']) : $request['id']; } $request['params'] += $params; }
/** * Get information about fields for a given api request. * * Getfields information is used for documentation, validation, default setting * We first query the scheme using the $dao->fields function & then augment * that information by calling the _spec functions that apply to the relevant function * Note that we use 'unique' field names as described in the xml/schema files * for get requests & just field name for create. This is because some get functions * access multiple objects e.g. contact api accesses is_deleted from the activity * table & from the contact table * * @param array $apiRequest * Api request as an array. Keys are. * - entity: string * - action: string * - version: string * - function: callback (mixed) * - params: array, varies * * @param bool $unique * Determines whether to key by unique field names (only affects get-type) actions * * @return array * API success object */ function civicrm_api3_generic_getfields($apiRequest, $unique = TRUE) { static $results = array(); if (CRM_Utils_Array::value('cache_clear', $apiRequest['params'])) { $results = array(); // we will also clear pseudoconstants here - should potentially be moved to relevant BAO classes CRM_Core_PseudoConstant::flush(); if (!empty($apiRequest['params']['fieldname'])) { CRM_Utils_PseudoConstant::flushConstant($apiRequest['params']['fieldname']); } if (!empty($apiRequest['params']['option_group_id'])) { $optionGroupName = civicrm_api('option_group', 'getvalue', array('version' => 3, 'id' => $apiRequest['params']['option_group_id'], 'return' => 'name')); if (is_string($optionGroupName)) { CRM_Utils_PseudoConstant::flushConstant(_civicrm_api_get_camel_name($optionGroupName)); } } } $entity = $apiRequest['entity']; $lowercase_entity = _civicrm_api_get_entity_name_from_camel($entity); $subentity = CRM_Utils_Array::value('contact_type', $apiRequest['params']); $action = CRM_Utils_Array::value('action', $apiRequest['params']); $sequential = empty($apiRequest['params']['sequential']) ? 0 : 1; $apiRequest['params']['options'] = CRM_Utils_Array::value('options', $apiRequest['params'], array()); $optionsToResolve = (array) CRM_Utils_Array::value('get_options', $apiRequest['params']['options'], array()); if (!$action || $action == 'getvalue' || $action == 'getcount') { $action = 'get'; } // If no options, return results from cache if (!$apiRequest['params']['options'] && isset($results[$entity . $subentity]) && isset($action, $results[$entity . $subentity]) && isset($action, $results[$entity . $subentity][$sequential])) { return $results[$entity . $subentity][$action][$sequential]; } // defaults based on data model and API policy switch ($action) { case 'getfields': $values = _civicrm_api_get_fields($entity, FALSE, $apiRequest['params']); return civicrm_api3_create_success($values, $apiRequest['params'], $entity, 'getfields'); case 'create': case 'update': case 'replace': $unique = FALSE; case 'get': case 'getsingle': case 'getcount': case 'getstat': $metadata = _civicrm_api_get_fields($apiRequest['entity'], $unique, $apiRequest['params']); if (empty($metadata['id'])) { // if id is not set we will set it eg. 'id' from 'case_id', case_id will be an alias if (!empty($metadata[strtolower($apiRequest['entity']) . '_id'])) { $metadata['id'] = $metadata[$lowercase_entity . '_id']; unset($metadata[$lowercase_entity . '_id']); $metadata['id']['api.aliases'] = array($lowercase_entity . '_id'); } } else { // really the preference would be to set the unique name in the xml // question is which is a less risky fix this close to a release - setting in xml for the known failure // (note) or setting for all api where fields is returning 'id' & we want to accept 'note_id' @ the api layer // nb we don't officially accept note_id anyway - rationale here is more about centralising a now-tested // inconsistency $metadata['id']['api.aliases'] = array($lowercase_entity . '_id'); } break; case 'delete': $metadata = array('id' => array('title' => $entity . ' ID', 'api.required' => 1, 'api.aliases' => array($lowercase_entity . '_id'), 'type' => CRM_Utils_Type::T_INT)); break; // Note: adding setvalue case here instead of in a generic spec function because // some APIs override the generic setvalue fn which causes the generic spec to be overlooked. // Note: adding setvalue case here instead of in a generic spec function because // some APIs override the generic setvalue fn which causes the generic spec to be overlooked. case 'setvalue': $metadata = array('field' => array('title' => 'Field name', 'api.required' => 1, 'type' => CRM_Utils_Type::T_STRING), 'id' => array('title' => $entity . ' ID', 'api.required' => 1, 'type' => CRM_Utils_Type::T_INT), 'value' => array('title' => 'Value', 'description' => "Field value to set", 'api.required' => 1)); if (array_intersect(array('all', 'field'), $optionsToResolve)) { $options = civicrm_api3_generic_getfields(array('entity' => $entity, array('params' => array('action' => 'create')))); $metadata['field']['options'] = CRM_Utils_Array::collect('title', $options['values']); } break; default: // oddballs are on their own $metadata = array(); } // Normalize this for the sake of spec funcions $apiRequest['params']['options']['get_options'] = $optionsToResolve; // find any supplemental information $hypApiRequest = array('entity' => $apiRequest['entity'], 'action' => $action, 'version' => $apiRequest['version']); try { list($apiProvider, $hypApiRequest) = \Civi::service('civi_api_kernel')->resolve($hypApiRequest); if (isset($hypApiRequest['function'])) { $helper = '_' . $hypApiRequest['function'] . '_spec'; } else { // not implemented MagicFunctionProvider $helper = NULL; } } catch (\Civi\API\Exception\NotImplementedException $e) { $helper = NULL; } if (function_exists($helper)) { // alter $helper($metadata, $apiRequest); } foreach ($metadata as $fieldname => $fieldSpec) { // Ensure 'name' is set if (!isset($fieldSpec['name'])) { $metadata[$fieldname]['name'] = $fieldname; } _civicrm_api3_generic_get_metadata_options($metadata, $apiRequest, $fieldname, $fieldSpec); // Convert options to "sequential" format if ($sequential && !empty($metadata[$fieldname]['options'])) { $metadata[$fieldname]['options'] = CRM_Utils_Array::makeNonAssociative($metadata[$fieldname]['options']); } } $results[$entity][$action][$sequential] = civicrm_api3_create_success($metadata, $apiRequest['params'], $entity, 'getfields'); return $results[$entity][$action][$sequential]; }
/** * Joins onto an fk field * * Adds one or more joins to the query to make this field available for use in a clause. * * Enforces permissions at the api level and by appending the acl clause for that entity to the join. * * @param $fkFieldName * @return array|null * Returns the table and field name for adding this field to a SELECT or WHERE clause * @throws \API_Exception * @throws \Civi\API\Exception\UnauthorizedException */ private function addFkField($fkFieldName) { $stack = explode('.', $fkFieldName); if (count($stack) < 2) { return NULL; } $prev = 'a'; foreach ($stack as $depth => $fieldName) { // Setup variables then skip the first level if (!$depth) { $fk = $fieldName; // We only join on core fields // @TODO: Custom contact ref fields could be supported too if (!in_array($fk, $this->entityFieldNames)) { return NULL; } $fkField =& $this->apiFieldSpec[$fk]; continue; } // More than 4 joins deep seems excessive - DOS attack? if ($depth > self::MAX_JOINS) { throw new UnauthorizedException("Maximum number of joins exceeded for api.{$this->entity}.get in parameter {$fkFieldName}"); } if (!isset($fkField['FKApiName']) && !isset($fkField['FKClassName'])) { // Join doesn't exist - might be another param with a dot in it for some reason, we'll just ignore it. return NULL; } // Ensure we have permission to access the other api if (!$this->checkPermissionToJoin($fkField['FKApiName'], array_slice($stack, 0, $depth))) { throw new UnauthorizedException("Authorization failed to join onto {$fkField['FKApiName']} api in parameter {$fkFieldName}"); } if (!isset($fkField['FKApiSpec'])) { $fkField['FKApiSpec'] = \_civicrm_api_get_fields($fkField['FKApiName']); } $fieldInfo = \CRM_Utils_Array::value($fieldName, $fkField['FKApiSpec']); // FIXME: What if the foreign key is not the "id" column? if (!$fieldInfo || !isset($fkField['FKApiSpec']['id'])) { // Join doesn't exist - might be another param with a dot in it for some reason, we'll just ignore it. return NULL; } $fkTable = \CRM_Core_DAO_AllCoreTables::getTableForClass($fkField['FKClassName']); $tableAlias = implode('_to_', array_slice($stack, 0, $depth)) . "_to_{$fkTable}"; $joinClause = "LEFT JOIN {$fkTable} {$tableAlias} ON {$prev}.{$fk} = {$tableAlias}.id"; // Add acl condition $joinCondition = $this->getAclClause($tableAlias, $fkField['FKClassName']); if ($joinCondition !== NULL) { $joinClause .= " AND {$joinCondition}"; } $this->query->join($tableAlias, $joinClause); if (strpos($fieldName, 'custom_') === 0) { list($tableAlias, $fieldName) = $this->addCustomField($fieldInfo, $tableAlias); } // Get ready to recurse to the next level $fk = $fieldName; $fkField =& $fkField['FKApiSpec'][$fieldName]; $prev = $tableAlias; } return array($tableAlias, $fieldName); }
/** * Get information about fields for a given api request. Getfields information * is used for documentation, validation, default setting * We first query the scheme using the $dao->fields function & then augment * that information by calling the _spec functions that apply to the relevant function * Note that we use 'unique' field names as described in the xml/schema files * for get requests & just field name for create. This is because some get functions * access multiple objects e.g. contact api accesses is_deleted from the activity * table & from the contact table * * @param array $apiRequest api request as an array. Keys are * - entity: string * - action: string * - version: string * - function: callback (mixed) * - params: array, varies * @return array API success object */ function civicrm_api3_generic_getfields($apiRequest) { static $results = array(); if (CRM_Utils_Array::value('cache_clear', $apiRequest['params'])) { $results = array(); // we will also clear pseudoconstants here - should potentially be moved to relevant BAO classes CRM_Core_PseudoConstant::flush(); if (!empty($apiRequest['params']['fieldname'])) { CRM_Utils_PseudoConstant::flushConstant($apiRequest['params']['fieldname']); } if (!empty($apiRequest['params']['option_group_id'])) { $optionGroupName = civicrm_api('option_group', 'getvalue', array('version' => 3, 'id' => $apiRequest['params']['option_group_id'], 'return' => 'name')); if (is_string($optionGroupName)) { CRM_Utils_PseudoConstant::flushConstant(_civicrm_api_get_camel_name($optionGroupName)); } } } $entity = _civicrm_api_get_camel_name($apiRequest['entity']); $lcase_entity = _civicrm_api_get_entity_name_from_camel($entity); $subentity = CRM_Utils_Array::value('contact_type', $apiRequest['params']); $action = strtolower(CRM_Utils_Array::value('action', $apiRequest['params'])); $sequential = empty($apiRequest['params']) ? 0 : 1; $apiOptions = CRM_Utils_Array::value('options', $apiRequest['params'], array()); if ($action == 'getvalue' || $action == 'getvalue' || $action == 'getcount') { $action = 'get'; } if (empty($action)) { $action = 'get'; } // determines whether to use unique field names - seem comment block above $unique = TRUE; if (empty($apiOptions) && isset($results[$entity . $subentity]) && isset($action, $results[$entity . $subentity]) && isset($action, $results[$entity . $subentity][$sequential])) { return $results[$entity . $subentity][$action][$sequential]; } // defaults based on data model and API policy switch ($action) { case 'getfields': $values = _civicrm_api_get_fields($entity, false, $apiRequest['params']); return civicrm_api3_create_success($values, $apiRequest['params'], $entity, 'getfields'); case 'create': case 'update': case 'replace': $unique = FALSE; case 'get': $metadata = _civicrm_api_get_fields($apiRequest['entity'], $unique, $apiRequest['params']); if (empty($metadata['id'])) { // if id is not set we will set it eg. 'id' from 'case_id', case_id will be an alias if (!empty($metadata[strtolower($apiRequest['entity']) . '_id'])) { $metadata['id'] = $metadata[$lcase_entity . '_id']; unset($metadata[$lcase_entity . '_id']); $metadata['id']['api.aliases'] = array($lcase_entity . '_id'); } } else { // really the preference would be to set the unique name in the xml // question is which is a less risky fix this close to a release - setting in xml for the known failure // (note) or setting for all api where fields is returning 'id' & we want to accept 'note_id' @ the api layer // nb we don't officially accept note_id anyway - rationale here is more about centralising a now-tested // inconsistency $metadata['id']['api.aliases'] = array($lcase_entity . '_id'); } break; case 'delete': $metadata = array('id' => array('title' => 'Unique Identifier', 'api.required' => 1, 'api.aliases' => array($lcase_entity . '_id'), 'type' => CRM_Utils_Type::T_INT)); break; case 'getoptions': $metadata = array('field' => array('title' => 'Field to retrieve options for', 'api.required' => 1), 'context' => array('title' => 'Context string')); break; default: // oddballs are on their own $metadata = array(); } // find any supplemental information $hypApiRequest = array('entity' => $apiRequest['entity'], 'action' => $action, 'version' => $apiRequest['version']); $hypApiRequest += _civicrm_api_resolve($hypApiRequest); $helper = '_' . $hypApiRequest['function'] . '_spec'; if (function_exists($helper)) { // alter $helper($metadata, $apiRequest); } $fieldsToResolve = (array) CRM_Utils_Array::value('get_options', $apiOptions, array()); foreach ($metadata as $fieldname => $fieldSpec) { _civicrm_api3_generic_get_metadata_options($metadata, $apiRequest['entity'], $fieldname, $fieldSpec, $fieldsToResolve); } $results[$entity][$action][$sequential] = civicrm_api3_create_success($metadata, $apiRequest['params'], NULL, 'getfields'); return $results[$entity][$action][$sequential]; }
/** * Get information about fields for a given api request. Getfields information * is used for documentation, validation, default setting * We first query the scheme using the $dao->fields function & then augment * that information by calling the _spec functions that apply to the relevant function * Note that we use 'unique' field names as described in the xml/schema files * for get requests & just field name for create. This is because some get functions * access multiple objects e.g. contact api accesses is_deleted from the activity * table & from the contact table * * @param array $apiRequest api request as an array. Keys are * - entity: string * - action: string * - version: string * - function: callback (mixed) * - params: array, varies * @return array API success object */ function civicrm_api3_generic_getfields($apiRequest) { static $results = array(); if (CRM_Utils_Array::value('cache_clear', $apiRequest['params'])) { $results = array(); } $entity = _civicrm_api_get_camel_name($apiRequest['entity']); $lcase_entity = _civicrm_api_get_entity_name_from_camel($entity); $subentity = CRM_Utils_Array::value('contact_type', $apiRequest['params']); $action = strtolower(CRM_Utils_Array::value('action', $apiRequest['params'])); if ($action == 'getvalue' || $action == 'getvalue' || $action == 'getcount') { $action = 'get'; } if (empty($action)) { if (CRM_Utils_Array::value($entity . $subentity, $results) && CRM_Utils_Array::value('values', $results[$entity . $subentity])) { return $results[$entity . $subentity]; } else { $values = _civicrm_api_get_fields($entity); $results[$entity] = civicrm_api3_create_success($values, $apiRequest['params'], $entity, 'getfields'); return $results[$entity]; } } // determines whether to use unique field names - seem comment block above $unique = TRUE; if (isset($results[$entity . $subentity]) && CRM_Utils_Array::value($action, $results[$entity])) { return $results[$entity . $subentity][$action]; } // defaults based on data model and API policy switch ($action) { case 'getfields': $values = _civicrm_api_get_fields($entity, $apiRequest['params']); $results[$entity][$action] = civicrm_api3_create_success($values, $apiRequest['params'], $entity, 'getfields'); return $results[$entity][$action]; case 'create': case 'update': case 'replace': $unique = FALSE; case 'get': $metadata = _civicrm_api_get_fields($apiRequest['entity'], $unique, $apiRequest['params']); if (empty($metadata['id']) && !empty($metadata[$apiRequest['entity'] . '_id'])) { $metadata['id'] = $metadata[$lcase_entity . '_id']; $metadata['id']['api.aliases'] = array($lcase_entity . '_id'); unset($metadata[$lcase_entity . '_id']); } break; case 'delete': $metadata = array('id' => array('title' => 'Unique Identifier', 'api.required' => 1, 'api.aliases' => array($lcase_entity . '_id'))); break; default: // oddballs are on their own $metadata = array(); } // find any supplemental information $hypApiRequest = array('entity' => $apiRequest['entity'], 'action' => $action, 'version' => $apiRequest['version']); $hypApiRequest += _civicrm_api_resolve($hypApiRequest); $helper = '_' . $hypApiRequest['function'] . '_spec'; if (function_exists($helper)) { // alter $helper($metadata); } foreach ($metadata as $fieldname => $field) { if (array_key_exists('pseudoconstant', $field) && !CRM_Utils_Array::value('FKClassName', $field)) { $options = civicrm_api('constant', 'get', array('version' => 3, 'name' => $field['pseudoconstant'])); if (is_array(CRM_Utils_Array::value('values', $options))) { $metadata[$fieldname]['options'] = $options['values']; } } if (array_key_exists('enumValues', $field)) { $metadata[$fieldname]['options'] = explode(',', $field['enumValues']); } } $results[$entity][$action] = civicrm_api3_create_success($metadata, $apiRequest['params'], NULL, 'getfields'); return $results[$entity][$action]; }