entity = _civicrm_api_get_entity_name_from_dao($dao); $this->params = $params; $this->isFillUniqueFields = $isFillUniqueFields; $this->options = _civicrm_api3_get_options_from_params($this->params); $this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($dao)); // Call this function directly instead of using the api wrapper to force unique field names off require_once 'api/v3/Generic.php'; $apiSpec = \civicrm_api3_generic_getfields(array('entity' => $this->entity, 'version' => 3, 'params' => array('action' => 'get')), FALSE); $this->apiFieldSpec = $apiSpec['values']; $this->query = \CRM_Utils_SQL_Select::from($dao->tableName() . " a"); $dao->free(); } public function run() { // $select_fields maps column names to the field names of the result values. $select_fields = $custom_fields = array(); // populate $select_fields $return_all_fields = (empty($this->options['return']) || !is_array($this->options['return'])); $return = $return_all_fields ? array_fill_keys($this->entityFieldNames, 1) : $this->options['return']; // core return fields foreach ($return as $field_name => $include) { if ($include) { $field = $this->getField($field_name); if ($field && in_array($field['name'], $this->entityFieldNames)) { // 'a.' is an alias for the entity table. $select_fields["a.{$field['name']}"] = $field['name']; } elseif ($include && strpos($field_name, '.')) { $fkField = $this->addFkField($field_name); if ($fkField) { $select_fields[implode('.', $fkField)] = $field_name; } } } } // Do custom fields IF the params contain the word "custom" or we are returning * if ($return_all_fields || strpos(json_encode($this->params), 'custom')) { $custom_fields = _civicrm_api3_custom_fields_for_entity($this->entity); foreach ($custom_fields as $cf_id => $custom_field) { $field_name = "custom_$cf_id"; if ($return_all_fields || !empty($this->options['return'][$field_name]) || // This is a tested format so we support it. !empty($this->options['return']['custom']) ) { list($table_name, $column_name) = $this->addCustomField($custom_field); if ($custom_field["data_type"] != "ContactReference") { // 'ordinary' custom field. We will select the value as custom_XX. $select_fields["$table_name.$column_name"] = $field_name; } else { // contact reference custom field. The ID will be stored in custom_XX_id. // custom_XX will contain the sort name of the contact. $this->query->join("c_$cf_id", "LEFT JOIN civicrm_contact c_$cf_id ON c_$cf_id.id = `$table_name`.`$column_name`"); $select_fields["$table_name.$column_name"] = $field_name . "_id"; // We will call the contact table for the join c_XX. $select_fields["c_$cf_id.sort_name"] = $field_name; } } } } // Always select the ID. $select_fields["a.id"] = "id"; // populate where_clauses foreach ($this->params as $key => $value) { $table_name = NULL; $column_name = NULL; if (substr($key, 0, 7) == 'filter.') { // Legacy support for old filter syntax per the test contract. // (Convert the style to the later one & then deal with them). $filterArray = explode('.', $key); $value = array($filterArray[1] => $value); $key = 'filters'; } // Legacy support for 'filter's construct. if ($key == 'filters') { foreach ($value as $filterKey => $filterValue) { if (substr($filterKey, -4, 4) == 'high') { $key = substr($filterKey, 0, -5); $value = array('<=' => $filterValue); } if (substr($filterKey, -3, 3) == 'low') { $key = substr($filterKey, 0, -4); $value = array('>=' => $filterValue); } if ($filterKey == 'is_current' || $filterKey == 'isCurrent') { // Is current is almost worth creating as a 'sql filter' in the DAO function since several entities have the // concept. $todayStart = date('Ymd000000', strtotime('now')); $todayEnd = date('Ymd235959', strtotime('now')); $this->query->where(array("(a.start_date <= '$todayStart' OR a.start_date IS NULL) AND (a.end_date >= '$todayEnd' OR a.end_date IS NULL) AND a.is_active = 1 ")); } } } $field = $this->getField($key); if ($field) { $key = $field['name']; } if (in_array($key, $this->entityFieldNames)) { $table_name = 'a'; $column_name = $key; } elseif (($cf_id = \CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) { list($table_name, $column_name) = $this->addCustomField($custom_fields[$cf_id]); } elseif (strpos($key, '.')) { $fkInfo = $this->addFkField($key); if ($fkInfo) { list($table_name, $column_name) = $fkInfo; } } // I don't know why I had to specifically exclude 0 as a key - wouldn't the others have caught it? // We normally silently ignore null values passed in - if people want IS_NULL they can use acceptedSqlOperator syntax. if ((!$table_name) || empty($key) || is_null($value)) { // No valid filter field. This might be a chained call or something. // Just ignore this for the $where_clause. continue; } if (!is_array($value)) { $this->query->where(array("`$table_name`.`$column_name` = @value"), array( "@value" => $value, )); } else { // We expect only one element in the array, of the form // "operator" => "rhs". $operator = \CRM_Utils_Array::first(array_keys($value)); if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators())) { $this->query->where(array( "{$table_name}.{$column_name} = @value"), array("@value" => $value) ); } else { $this->query->where(\CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value)); } } } if (!$this->options['is_count']) { foreach ($select_fields as $column => $alias) { $this->query->select("$column as `$alias`"); } } else { $this->query->select("count(*) as c"); } // order by if (!empty($this->options['sort'])) { $sort_fields = array(); foreach (explode(',', $this->options['sort']) as $sort_option) { $words = preg_split("/[\s]+/", $sort_option); if (count($words) > 0 && in_array($words[0], array_values($select_fields))) { $tmp = $words[0]; if (!empty($words[1]) && strtoupper($words[1]) == 'DESC') { $tmp .= " DESC"; } $sort_fields[] = $tmp; } } if (count($sort_fields) > 0) { $this->query->orderBy(implode(",", $sort_fields)); } } // limit if (!empty($this->options['limit']) || !empty($this->options['offset'])) { $this->query->limit($this->options['limit'], $this->options['offset']); } $result_entities = array(); $result_dao = \CRM_Core_DAO::executeQuery($this->query->toSQL()); while ($result_dao->fetch()) { if ($this->options['is_count']) { $result_dao->free(); return (int) $result_dao->c; } $result_entities[$result_dao->id] = array(); foreach ($select_fields as $column => $alias) { $returnName = $alias; $alias = str_replace('.', '_', $alias); if (property_exists($result_dao, $alias) && $result_dao->$alias != NULL) { $result_entities[$result_dao->id][$returnName] = $result_dao->$alias; } // Backward compatibility on fields names. if ($this->isFillUniqueFields && !empty($this->apiFieldSpec[$alias]['uniqueName'])) { $result_entities[$result_dao->id][$this->apiFieldSpec[$alias]['uniqueName']] = $result_dao->$alias; } foreach ($this->apiFieldSpec as $returnName => $spec) { if (empty($result_entities[$result_dao->id][$returnName]) && !empty($result_entities[$result_dao->id][$spec['name']])) { $result_entities[$result_dao->id][$returnName] = $result_entities[$result_dao->id][$spec['name']]; } } }; } $result_dao->free(); return $result_entities; } /** * @param \CRM_Utils_SQL_Select $sqlFragment * @return $this */ public function merge($sqlFragment) { $this->query->merge($sqlFragment); return $this; } /** * Joins onto an fk field * * Adds one or more joins to the query to make this field available for use in a clause. * * @param $fkFieldName * @return array|null * Returns the table and field name for adding this field to a SELECT or WHERE clause */ 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; } if (!isset($fkField['FKApiName']) && !isset($fkField['FKClassName'])) { return NULL; } 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'])) { return NULL; } $fkTable = \CRM_Core_DAO_AllCoreTables::getTableForClass($fkField['FKClassName']); $tableAlias = "{$fk}_to_$fkTable"; $this->query->join($tableAlias, "LEFT JOIN $fkTable $tableAlias ON $prev.$fk = $tableAlias.id"); 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); } /** * Joins onto a custom field * * Adds a join to the query to make this field available for use in a clause. * * @param array $customField * @param string $baseTable * @return array * Returns the table and field name for adding this field to a SELECT or WHERE clause */ private function addCustomField($customField, $baseTable = 'a') { $tableName = $customField["table_name"]; $columnName = $customField["column_name"]; $tableAlias = "{$baseTable}_to_$tableName"; $this->query->join($tableAlias, "LEFT JOIN `$tableName` `$tableAlias` ON `$tableAlias`.entity_id = `$baseTable`.id"); return array($tableAlias, $columnName); } /** * Fetch a field from the getFields list * * Searches by name, uniqueName, and api.aliases * * @param string $fieldName * @return array|null */ private function getField($fieldName) { if (!$fieldName) { return NULL; } if (isset($this->apiFieldSpec[$fieldName])) { return $this->apiFieldSpec[$fieldName]; } foreach ($this->apiFieldSpec as $field) { if ( $fieldName == \CRM_Utils_Array::value('uniqueName', $field) || array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, array())) !== FALSE ) { return $field; } } return NULL; } }