Merge pull request #14928 from lcdservices/dev-core-1158
[civicrm-core.git] / Civi / API / Api3SelectQuery.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11 namespace Civi\API;
12
13 /**
14 */
15 class Api3SelectQuery extends SelectQuery {
16
17 protected $apiVersion = 3;
18
19 /**
20 * @inheritDoc
21 */
22 protected function buildWhereClause() {
23 $filters = [];
24 foreach ($this->where as $key => $value) {
25 $table_name = NULL;
26 $column_name = NULL;
27
28 if (substr($key, 0, 7) == 'filter.') {
29 // Legacy support for old filter syntax per the test contract.
30 // (Convert the style to the later one & then deal with them).
31 $filterArray = explode('.', $key);
32 $value = [$filterArray[1] => $value];
33 $key = 'filters';
34 }
35
36 // Legacy support for 'filter's construct.
37 if ($key == 'filters') {
38 foreach ($value as $filterKey => $filterValue) {
39 if (substr($filterKey, -4, 4) == 'high') {
40 $key = substr($filterKey, 0, -5);
41 $value = ['<=' => $filterValue];
42 }
43
44 if (substr($filterKey, -3, 3) == 'low') {
45 $key = substr($filterKey, 0, -4);
46 $value = ['>=' => $filterValue];
47 }
48
49 if ($filterKey == 'is_current' || $filterKey == 'isCurrent') {
50 // Is current is almost worth creating as a 'sql filter' in the DAO function since several entities have the concept.
51 $todayStart = date('Ymd000000', strtotime('now'));
52 $todayEnd = date('Ymd235959', strtotime('now'));
53 $a = self::MAIN_TABLE_ALIAS;
54 $this->query->where("($a.start_date <= '$todayStart' OR $a.start_date IS NULL)
55 AND ($a.end_date >= '$todayEnd' OR $a.end_date IS NULL)
56 AND a.is_active = 1");
57 }
58 }
59 }
60 // Ignore the "options" param if it is referring to api options and not a field in this entity
61 if (
62 $key === 'options' && is_array($value)
63 && !in_array(\CRM_Utils_Array::first(array_keys($value)), \CRM_Core_DAO::acceptedSQLOperators())
64 ) {
65 continue;
66 }
67 $field = $this->getField($key);
68 if ($field) {
69 $key = $field['name'];
70 }
71 if (in_array($key, $this->entityFieldNames)) {
72 $table_name = self::MAIN_TABLE_ALIAS;
73 $column_name = $key;
74 }
75 elseif (($cf_id = \CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) {
76 // If we check a custom field on 'IS NULL', it should also work when there is no
77 // record in the custom value table, see CRM-20740.
78 $side = empty($value['IS NULL']) ? 'INNER' : 'LEFT OUTER';
79 list($table_name, $column_name) = $this->addCustomField($this->apiFieldSpec['custom_' . $cf_id], $side);
80 }
81 elseif (strpos($key, '.')) {
82 $fkInfo = $this->addFkField($key, 'INNER');
83 if ($fkInfo) {
84 list($table_name, $column_name) = $fkInfo;
85 $this->validateNestedInput($key, $value);
86 }
87 }
88 // I don't know why I had to specifically exclude 0 as a key - wouldn't the others have caught it?
89 // We normally silently ignore null values passed in - if people want IS_NULL they can use acceptedSqlOperator syntax.
90 if ((!$table_name) || empty($key) || is_null($value)) {
91 // No valid filter field. This might be a chained call or something.
92 // Just ignore this for the $where_clause.
93 continue;
94 }
95 $operator = is_array($value) ? \CRM_Utils_Array::first(array_keys($value)) : NULL;
96 if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
97 $value = ['=' => $value];
98 }
99 $filters[$key] = \CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value);
100 }
101 // Support OR groups
102 if (!empty($this->where['options']['or'])) {
103 $orGroups = $this->where['options']['or'];
104 if (is_string($orGroups)) {
105 $orGroups = array_map('trim', explode(',', $orGroups));
106 }
107 if (!is_array(\CRM_Utils_Array::first($orGroups))) {
108 $orGroups = [$orGroups];
109 }
110 foreach ($orGroups as $orGroup) {
111 $orClause = [];
112 foreach ($orGroup as $key) {
113 if (!isset($filters[$key])) {
114 throw new \CiviCRM_API3_Exception("'$key' specified in OR group but not added to params");
115 }
116 $orClause[] = $filters[$key];
117 unset($filters[$key]);
118 }
119 $this->query->where(implode(' OR ', $orClause));
120 }
121 }
122 // Add the remaining params using AND
123 foreach ($filters as $filter) {
124 $this->query->where($filter);
125 }
126 }
127
128 /**
129 * @inheritDoc
130 */
131 protected function getFields() {
132 require_once 'api/v3/Generic.php';
133 // Call this function directly instead of using the api wrapper to force unique field names off
134 $apiSpec = \civicrm_api3_generic_getfields([
135 'entity' => $this->entity,
136 'version' => 3,
137 'params' => ['action' => 'get'],
138 ], FALSE);
139 return $apiSpec['values'];
140 }
141
142 /**
143 * Fetch a field from the getFields list
144 *
145 * Searches by name, uniqueName, and api.aliases
146 *
147 * @param string $fieldName
148 * Field name.
149 * @return NULL|mixed
150 */
151 protected function getField($fieldName) {
152 if (!$fieldName) {
153 return NULL;
154 }
155 if (isset($this->apiFieldSpec[$fieldName])) {
156 return $this->apiFieldSpec[$fieldName];
157 }
158 foreach ($this->apiFieldSpec as $field) {
159 if (
160 $fieldName == \CRM_Utils_Array::value('uniqueName', $field) ||
161 array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, [])) !== FALSE
162 ) {
163 return $field;
164 }
165 }
166 return NULL;
167 }
168
169 }