CRM-16036 stop overwriting of where clauses
[civicrm-core.git] / api / v3 / utils.php
index bdb6102f58d3708bda6877f98cbdfa4b69436322..16a3fd7e83711736d82a991c69a7d5781aaddf68 100644 (file)
@@ -468,29 +468,39 @@ function _civicrm_api3_store_values(&$fields, &$params, &$values) {
  *   Name of DAO
  * @param array $params
  *   As passed into api get function.
- * @param bool $return_as_success
- *   Return in api success format.
+ * @param bool $isFillUniqueFields
+ *   Do we need to ensure unique fields continue to be populated for this api? (backward compatibility).
+ * @param array $extraMysql
  *
  * @return array
  */
-function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return_as_success = TRUE) {
+function _civicrm_api3_get_using_utils_sql($dao_name, $params, $isFillUniqueFields, $extraMysql) {
+
   $dao = new $dao_name();
   $entity = _civicrm_api_get_entity_name_from_dao($dao);
   $custom_fields = _civicrm_api3_custom_fields_for_entity($entity);
   $options = _civicrm_api3_get_options_from_params($params);
+  // Unset $params['options'] if they are api parameters (not options as a fieldname).
+  if (!empty($params['options']) && is_array($params['options'])&& array_intersect(array_keys($params['options']), array_keys($options))) {
+    unset ($params['options']);
+  }
 
-  $entity_field_names = _civicrm_api3_field_names(
-    _civicrm_api3_build_fields_array($dao));
-
+  $entity_field_names = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($dao));
+  $custom_field_names = array();
+  $getFieldsResult = civicrm_api3($entity, 'getfields', array('action' => 'get'));
+  $getFieldsResult = $getFieldsResult['values'];
   // $select_fields maps column names to the field names of the result
   // values.
   $select_fields = array();
 
-  // array with elements array('colummn', 'operator', 'value');
+  // array with elements array('column', 'operator', 'value');
   $where_clauses = array();
 
   // Tables we need to join with to retrieve the custom values.
-  $tables_to_join = array();
+  $custom_value_tables = array();
+
+  // ID's of custom fields that refer to a contact.
+  $contact_reference_field_ids = array();
 
   // populate $select_fields
   $return_all_fields = (empty($options['return']) || !is_array($options['return']));
@@ -506,13 +516,30 @@ function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return
   // custom fields
   foreach ($custom_fields as $cf_id => $custom_field) {
     $field_name = "custom_$cf_id";
-    if ($return_all_fields || !empty($options['return'][$field_name])) {
+    $custom_field_names[] = $field_name;
+    if ($return_all_fields || !empty($options['return'][$field_name])
+      ||
+      // This is a tested format so we support it.
+      !empty($options['return']['custom'])
+    ) {
       $table_name = $custom_field["table_name"];
       $column_name = $custom_field["column_name"];
-      $select_fields["$table_name.$column_name"] = "custom_$cf_id";
       // remember that we will need to join the correct table.
-      if (!in_array($table_name, $tables_to_join)) {
-        $tables_to_join[] = $table_name;
+      if (!in_array($table_name, $custom_value_tables)) {
+        $custom_value_tables[] = $table_name;
+      }
+      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.
+        $contact_reference_field_ids[] = $cf_id;
+        $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;
       }
     }
   }
@@ -520,54 +547,71 @@ function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return
     // Always select the ID.
     $select_fields["a.id"] = "id";
   }
+  // build query
+  $query = CRM_Utils_SQL_Select::from($dao->tableName() . " a");
 
   // populate $where_clauses
   foreach ($params as $key => $value) {
+    $type = 'String';
+    if (array_key_exists($key, $getFieldsResult)) {
+      $type = $getFieldsResult[$key]['type'];
+      $key = $getFieldsResult[$key]['name'];
+    }
+    if (in_array($key, $entity_field_names)) {
+      $table_name = 'a';
+      $column_name = $key;
+    }
+    elseif (($cf_id = CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) {
+      $table_name = $custom_fields[$cf_id]["table_name"];
+      $column_name = $custom_fields[$cf_id]["column_name"];
+
+      if (!in_array($table_name, $custom_value_tables)) {
+        $custom_value_tables[] = $table_name;
+      }
+    }
+    // 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 ((!in_array($key, $entity_field_names) && !in_array($key, $custom_field_names)) || 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)) {
-      $operator = "=";
-      $rhs = $value;
+      $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())) {
-        // if operator not found, use = as default.
-        $operator = "=";
-      }
-      $rhs = $value[$operator];
-    }
-
-    if (in_array($key, $entity_field_names)) {
-      $where_clauses[] = array("a.$key", $operator, $rhs);
-    }
-    else {
-      $cf_id = CRM_Core_BAO_CustomField::getKeyID($key);
-      if ($cf_id) {
-        $table_name = $custom_fields[$cf_id]["table_name"];
-        $column_name = $custom_fields[$cf_id]["column_name"];
-        $where_clauses[] = array(
-          "$table_name.$column_name",
-          $operator,
-          $rhs,
+        $query->where(array(
+          "{$table_name}.{$column_name} = @value"), array("@value" => $value)
         );
-        if (!in_array($table_name, $tables_to_join)) {
-          $tables_to_join[] = $table_name;
-        }
+      }
+      else {
+        $query->where(CRM_Core_DAO::createSQLFilter('a.' . $column_name, $value, $type));
       }
     }
-  };
-
-  // build query
-  $query = CRM_Utils_SQL_Select::from($dao->tableName() . " a");
+  }
 
   $i = 0;
-  foreach ($select_fields as $column => $alias) {
-    ++$i;
-    $query = $query->select("!column_$i as !alias_$i", array("!column_$i" => $column, "!alias_$i" => $alias));
+  if (!$options['is_count']) {
+    foreach ($select_fields as $column => $alias) {
+      ++$i;
+      $query = $query->select("!column_$i as !alias_$i", array(
+        "!column_$i" => $column,
+        "!alias_$i" => $alias,
+      ));
+    }
+  }
+  else {
+    $query->select("count(*) as c");
   }
 
-  foreach ($tables_to_join as $table_name) {
+  // join with custom value tables
+  foreach ($custom_value_tables as $table_name) {
     ++$i;
     $query = $query->join(
       "!table_name_$i",
@@ -576,6 +620,19 @@ function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return
     );
   }
 
+  // join with contact for contact reference fields
+  foreach ($contact_reference_field_ids as $field_id) {
+    ++$i;
+    $query = $query->join(
+      "!contact_table_name$i",
+      "LEFT OUTER JOIN civicrm_contact !contact_table_name_$i ON !contact_table_name_$i.id = !values_table_name_$i.!column_name_$i",
+      array(
+        "!contact_table_name_$i" => "c_$field_id",
+        "!values_table_name_$i" => $custom_fields[$field_id]["table_name"],
+        "!column_name_$i" => $custom_fields[$field_id]["column_name"],
+      ));
+  };
+
   foreach ($where_clauses as $clause) {
     ++$i;
     if (substr($clause[1], -4) == "NULL") {
@@ -592,10 +649,15 @@ function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return
       ));
     }
   };
+  if (!empty($extraMysql['where'])) {
+    foreach ($extraMysql['where'] as $extraWhere) {
+      $query->where($extraWhere);
+    }
+  }
 
   // order by
   if (!empty($options['sort'])) {
-    $sort_fiels = array();
+    $sort_fields = array();
     foreach (explode(',', $options['sort']) as $sort_option) {
       $words = preg_split("/[\s]+/", $sort_option);
       if (count($words) > 0 && in_array($words[0], array_values($select_fields))) {
@@ -618,28 +680,40 @@ function _civicrm_api3_get_using_query_object_simple($dao_name, $params, $return
 
   $result_entities = array();
   $result_dao = CRM_Core_DAO::executeQuery($query->toSQL());
+
   while ($result_dao->fetch()) {
+    if ($options['is_count']) {
+      $result_dao->free();
+      return (int) $result_dao->c;
+    }
     $result_entities[$result_dao->id] = array();
     foreach ($select_fields as $column => $alias) {
-      if (array_key_exists($alias, $result_dao)) {
+      if (property_exists($result_dao, $alias)) {
         $result_entities[$result_dao->id][$alias] = $result_dao->$alias;
       }
+      // Backward compatibility on fields names.
+      if ($isFillUniqueFields && !empty($getFieldsResult['values'][$column]['uniqueName'])) {
+        $result_entities[$result_dao->id][$getFieldsResult['values'][$column]['uniqueName']] = $result_dao->$alias;
+      }
+      foreach ($getFieldsResult as $returnName => $spec) {
+        $castToInt = FALSE;
+        if (!empty($spec['type']) && $spec['type'] == CRM_Utils_Type::T_INT) {
+          $castToInt = TRUE;
+        }
+        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();
-
-  if ($return_as_success) {
-    return civicrm_api3_create_success($result_entities, $params, $entity, 'get', $dao);
-  }
-  else {
-    return $result_entities;
-  }
+  return $result_entities;
 }
 
 /**
  * Returns field names of the given entity fields.
  *
- * @param string $fields
+ * @param array $fields
  *   Fields array to retrieve the field names for.
  * @return array
  */
@@ -668,7 +742,8 @@ function _civicrm_api3_field_names($fields) {
  *   {
  *     '1' => array {
  *       'table_name' => 'table_name_1',
- *       'column_name' => ''column_name_1',
+ *       'column_name' => 'column_name_1',
+ *       'data_type' => 'data_type_1',
  *     },
  *   }
  */
@@ -696,6 +771,7 @@ SELECT f.id, f.label, f.data_type,
     $result[$dao->id] = array(
       'table_name' => $dao->table_name,
       'column_name' => $dao->column_name,
+      'data_type' => $dao->data_type,
     );
   }
   $dao->free();
@@ -862,11 +938,14 @@ function _civicrm_api3_get_query_object($params, $mode, $entity) {
  * @param CRM_Core_DAO $dao
  * @param array $params
  * @param bool $unique
+ * @param array $extraSql
+ *   API specific queries eg for event isCurrent would be converted to
+ *   $extraSql['where'] = array('civicrm_event' => array('(start_date >= CURDATE() || end_date >= CURDATE())'));
  *
  * @throws API_Exception
  * @throws Exception
  */
-function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE) {
+function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE, $extraSql = array()) {
   $entity = _civicrm_api_get_entity_name_from_dao($dao);
   $lowercase_entity = _civicrm_api_get_entity_name_from_camel($entity);
   if (!empty($params[$lowercase_entity . "_id"]) && empty($params['id'])) {
@@ -922,6 +1001,13 @@ function _civicrm_api3_dao_set_filter(&$dao, $params, $unique = TRUE) {
       }
     }
   }
+  if (!empty($extraSql['where'])) {
+    foreach ($extraSql['where'] as $table => $sqlWhere) {
+      foreach ($sqlWhere as $where) {
+        $dao->whereAdd($where);
+      }
+    }
+  }
   if (!empty($options['return']) && is_array($options['return']) && empty($options['is_count'])) {
     $dao->selectAdd();
     // Ensure 'id' is included.
@@ -1061,7 +1147,7 @@ function _civicrm_api3_get_options_from_params(&$params, $queryObject = FALSE, $
     return $options;
   }
   //here comes the legacy support for $returnProperties, $inputParams e.g for contat_get
-  // if the queryobject is being used this should be used
+  // if the query object is being used this should be used
   $inputParams = array();
   $legacyreturnProperties = array();
   $otherVars = array(
@@ -1185,8 +1271,7 @@ function _civicrm_api3_dao_to_array($dao, $params = NULL, $uniqueFields = TRUE,
     return $dao->count;
   }
 
-  $fields = array_keys(_civicrm_api3_build_fields_array($dao, $uniqueFields));
-
+  $fields = array_keys(_civicrm_api3_build_fields_array($dao, FALSE));
   while ($dao->fetch()) {
     $tmp = array();
     foreach ($fields as $key) {
@@ -1231,6 +1316,7 @@ function _civicrm_api3_custom_fields_are_required($entity, $params) {
     return TRUE;
   }
 }
+
 /**
  * Converts an object to an array.
  *
@@ -1492,26 +1578,21 @@ function _civicrm_api3_check_required_fields($params, $daoName, $return = FALSE)
  * @param bool $returnAsSuccess
  *   Return in api success format.
  * @param string $entity
+ * @param array $extraSql
+ *   API specific queries eg for event isCurrent would be converted to
+ *   $extraSql['where'] = array('civicrm_event' => array('(start_date >= CURDATE() || end_date >= CURDATE())'));
+ * @param bool $uniqueFields
+ *   Should unique field names be returned (for backward compatibility)
  *
  * @return array
  */
-function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $entity = "") {
-  // if $params refers to a custom field, use a hack to
-  // avoid CRM-16036
-  foreach (array_keys($params) as $key) {
-    if (substr($key, 0, 7) == 'custom_') {
-      return _civicrm_api3_get_using_query_object_simple(
-        $bao_name, $params, $returnAsSuccess);
-    }
-  }
+function _civicrm_api3_basic_get($bao_name, &$params, $returnAsSuccess = TRUE, $entity = "", $extraSql = array(), $uniqueFields = FALSE) {
 
-  $bao = new $bao_name();
-  _civicrm_api3_dao_set_filter($bao, $params, TRUE);
   if ($returnAsSuccess) {
-    return civicrm_api3_create_success(_civicrm_api3_dao_to_array($bao, $params, FALSE, $entity), $params, $entity, 'get');
+    return civicrm_api3_create_success(_civicrm_api3_get_using_utils_sql($bao_name, $params, $uniqueFields, $extraSql), $params, $entity, 'get');
   }
   else {
-    return _civicrm_api3_dao_to_array($bao, $params, FALSE, $entity, 'get');
+    return _civicrm_api3_get_using_utils_sql($bao_name, $params, $uniqueFields, $extraSql);
   }
 }
 
@@ -2452,8 +2533,10 @@ function _civicrm_api3_api_match_pseudoconstant_value(&$value, $options, $fieldN
  * @param $fieldName
  *   any variation of a field's name (name, unique_name, api.alias).
  *
+ * @param string $action
+ *
  * @return bool|string
- *   fieldName or FALSE if the field does not exist
+ *   FieldName or FALSE if the field does not exist
  */
 function _civicrm_api3_api_resolve_alias($entity, $fieldName, $action = 'create') {
   if (!$fieldName) {
@@ -2544,6 +2627,7 @@ function _civicrm_api3_field_value_check(&$params, $fieldName, $type = NULL) {
  * _civicrm_api3_basic_get but does not use DAO/BAO. This is useful for
  * small/mid-size data loaded from external JSON or XML documents.
  *
+ * @param $entity
  * @param array $params
  *   API parameters.
  * @param array $records
@@ -2552,7 +2636,9 @@ function _civicrm_api3_field_value_check(&$params, $fieldName, $type = NULL) {
  *   The property which defines the ID of a record
  * @param array $fields
  *   List of filterable fields.
+ *
  * @return array
+ * @throws \API_Exception
  */
 function _civicrm_api3_basic_array_get($entity, $params, $records, $idCol, $fields) {
   $options = _civicrm_api3_get_options_from_params($params, TRUE, $entity, 'get');