Merge pull request #16488 from mattwire/quickform_requiredfields
[civicrm-core.git] / Civi / Api4 / Query / Api4SelectQuery.php
CommitLineData
19b53e5b
C
1<?php
2/*
3 +--------------------------------------------------------------------+
41498ac5 4 | Copyright CiviCRM LLC. All rights reserved. |
19b53e5b 5 | |
41498ac5
TO
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 |
19b53e5b
C
9 +--------------------------------------------------------------------+
10 */
11
12namespace Civi\Api4\Query;
13
14use Civi\API\SelectQuery;
19b53e5b 15use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
19b53e5b
C
16use Civi\Api4\Utils\FormattingUtil;
17use Civi\Api4\Utils\CoreUtil;
39e0f675 18use Civi\Api4\Utils\SelectUtil;
19b53e5b
C
19
20/**
21 * A query `node` may be in one of three formats:
22 *
23 * * leaf: [$fieldName, $operator, $criteria]
24 * * negated: ['NOT', $node]
25 * * branch: ['OR|NOT', [$node, $node, ...]]
26 *
27 * Leaf operators are one of:
28 *
29 * * '=', '<=', '>=', '>', '<', 'LIKE', "<>", "!=",
30 * * "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN',
31 * * 'IS NOT NULL', or 'IS NULL'.
32 */
33class Api4SelectQuery extends SelectQuery {
34
35 /**
36 * @var int
37 */
38 protected $apiVersion = 4;
39
3176b04c
CW
40 /**
41 * @var array
37d82abe 42 * [alias => expr][]
3176b04c
CW
43 */
44 protected $selectAliases = [];
45
b65fa6dc
CW
46 /**
47 * If set to an array, this will start collecting debug info.
48 *
49 * @var null|array
50 */
51 public $debugOutput = NULL;
52
fba513f6
CW
53 /**
54 * @var array
55 */
56 public $groupBy = [];
57
48102254
CW
58 public $forceSelectId = TRUE;
59
c9e3ae2e
CW
60 /**
61 * @var array
62 */
63 public $having = [];
64
19b53e5b 65 /**
3c7c8fa6
CW
66 * @param \Civi\Api4\Generic\DAOGetAction $apiGet
67 */
68 public function __construct($apiGet) {
69 $this->entity = $apiGet->getEntityName();
70 $this->checkPermissions = $apiGet->getCheckPermissions();
71 $this->select = $apiGet->getSelect();
72 $this->where = $apiGet->getWhere();
fba513f6 73 $this->groupBy = $apiGet->getGroupBy();
3c7c8fa6
CW
74 $this->orderBy = $apiGet->getOrderBy();
75 $this->limit = $apiGet->getLimit();
76 $this->offset = $apiGet->getOffset();
c9e3ae2e 77 $this->having = $apiGet->getHaving();
48102254
CW
78 // Always select ID of main table unless grouping is used
79 $this->forceSelectId = !$this->groupBy;
3c7c8fa6
CW
80 if ($apiGet->getDebug()) {
81 $this->debugOutput =& $apiGet->_debugOutput;
82 }
5e327f37
CW
83 foreach ($apiGet->entityFields() as $field) {
84 $this->entityFieldNames[] = $field['name'];
9b06167d 85 $field['sql_name'] = '`' . self::MAIN_TABLE_ALIAS . '`.`' . $field['column_name'] . '`';
5e327f37 86 $this->addSpecField($field['name'], $field);
a689294c 87 }
19b53e5b 88
5e327f37
CW
89 $baoName = CoreUtil::getBAOFromApiName($this->entity);
90 $this->constructQueryObject();
19b53e5b
C
91
92 // Add ACLs first to avoid redundant subclauses
93 $this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName));
16f5a13d
CW
94
95 // Add explicit joins. Other joins implied by dot notation may be added later
96 $this->addExplicitJoins($apiGet->getJoin());
19b53e5b
C
97 }
98
99 /**
4e97c268 100 * Builds final sql statement after all params are set.
19b53e5b 101 *
4e97c268
CW
102 * @return string
103 * @throws \API_Exception
104 * @throws \CRM_Core_Exception
105 * @throws \Civi\API\Exception\UnauthorizedException
19b53e5b 106 */
4e97c268 107 public function getSql() {
a689294c 108 $this->buildSelectClause();
19b53e5b 109 $this->buildWhereClause();
a689294c
CW
110 $this->buildOrderBy();
111 $this->buildLimit();
fba513f6 112 $this->buildGroupBy();
c9e3ae2e 113 $this->buildHavingClause();
4e97c268
CW
114 return $this->query->toSQL();
115 }
19b53e5b 116
4e97c268
CW
117 /**
118 * Why walk when you can
119 *
120 * @return array|int
121 */
122 public function run() {
19b53e5b 123 $results = [];
4e97c268 124 $sql = $this->getSql();
b65fa6dc 125 if (is_array($this->debugOutput)) {
0f7babcc 126 $this->debugOutput['sql'][] = $sql;
b65fa6dc 127 }
19b53e5b 128 $query = \CRM_Core_DAO::executeQuery($sql);
19b53e5b
C
129 while ($query->fetch()) {
130 if (in_array('row_count', $this->select)) {
131 $results[]['row_count'] = (int) $query->c;
132 break;
133 }
334bebdd 134 $result = [];
c9e3ae2e 135 foreach ($this->selectAliases as $alias => $expr) {
19b53e5b 136 $returnName = $alias;
3176b04c 137 $alias = str_replace('.', '_', $alias);
334bebdd 138 $result[$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
fba513f6 139 }
334bebdd 140 $results[] = $result;
19b53e5b 141 }
334bebdd
CW
142 FormattingUtil::formatOutputValues($results, $this->getApiFieldSpec(), $this->getEntity());
143 return $results;
19b53e5b
C
144 }
145
a689294c 146 protected function buildSelectClause() {
9b06167d 147 // An empty select is the same as *
a689294c
CW
148 if (empty($this->select)) {
149 $this->select = $this->entityFieldNames;
19b53e5b 150 }
a689294c
CW
151 elseif (in_array('row_count', $this->select)) {
152 $this->query->select("COUNT(*) AS `c`");
153 return;
19b53e5b 154 }
a689294c 155 else {
48102254 156 if ($this->forceSelectId) {
fba513f6
CW
157 $this->select = array_merge(['id'], $this->select);
158 }
a689294c
CW
159
160 // Expand wildcards in joins (the api wrapper already expanded non-joined wildcards)
161 $wildFields = array_filter($this->select, function($item) {
3176b04c 162 return strpos($item, '*') !== FALSE && strpos($item, '.') !== FALSE && strpos($item, '(') === FALSE && strpos($item, ' ') === FALSE;
a689294c
CW
163 });
164 foreach ($wildFields as $item) {
165 $pos = array_search($item, array_values($this->select));
334bebdd 166 $this->autoJoinFK($item);
a689294c
CW
167 $matches = SelectUtil::getMatchingFields($item, array_keys($this->apiFieldSpec));
168 array_splice($this->select, $pos, 1, $matches);
169 }
170 $this->select = array_unique($this->select);
171 }
3176b04c
CW
172 foreach ($this->select as $item) {
173 $expr = SqlExpression::convert($item, TRUE);
174 $valid = TRUE;
175 foreach ($expr->getFields() as $fieldName) {
176 $field = $this->getField($fieldName);
177 // Remove expressions with unknown fields without raising an error
178 if (!$field) {
179 $this->select = array_diff($this->select, [$item]);
180 if (is_array($this->debugOutput)) {
181 $this->debugOutput['undefined_fields'][] = $fieldName;
182 }
183 $valid = FALSE;
184 }
19b53e5b 185 }
3176b04c 186 if ($valid) {
c9e3ae2e 187 $alias = $expr->getAlias();
19fde02c
CW
188 if ($alias != $expr->getExpr() && isset($this->apiFieldSpec[$alias])) {
189 throw new \API_Exception('Cannot use existing field name as alias');
190 }
c9e3ae2e 191 $this->selectAliases[$alias] = $expr->getExpr();
3176b04c 192 $this->query->select($expr->render($this->apiFieldSpec) . " AS `$alias`");
9b06167d 193 }
19b53e5b
C
194 }
195 }
196
197 /**
198 * @inheritDoc
199 */
200 protected function buildWhereClause() {
201 foreach ($this->where as $clause) {
0c9bccf1
CW
202 $sql = $this->treeWalkClauses($clause, 'WHERE');
203 if ($sql) {
204 $this->query->where($sql);
205 }
c9e3ae2e
CW
206 }
207 }
208
209 /**
210 * Build HAVING clause.
211 *
212 * Every expression referenced must also be in the SELECT clause.
213 */
214 protected function buildHavingClause() {
215 foreach ($this->having as $clause) {
216 $this->query->having($this->treeWalkClauses($clause, 'HAVING'));
19b53e5b
C
217 }
218 }
219
220 /**
221 * @inheritDoc
222 */
223 protected function buildOrderBy() {
3176b04c 224 foreach ($this->orderBy as $item => $dir) {
19b53e5b 225 if ($dir !== 'ASC' && $dir !== 'DESC') {
3176b04c
CW
226 throw new \API_Exception("Invalid sort direction. Cannot order by $item $dir");
227 }
a4499ec5
CW
228 $expr = $this->getExpression($item);
229 $column = $expr->render($this->apiFieldSpec);
230
231 // Use FIELD() function to sort on pseudoconstant values
232 $suffix = strstr($item, ':');
233 if ($suffix && $expr->getType() === 'SqlField') {
234 $field = $this->getField($item);
235 $options = FormattingUtil::getPseudoconstantList($field['entity'], $field['name'], substr($suffix, 1));
236 if ($options) {
237 asort($options);
238 $column = "FIELD($column,'" . implode("','", array_keys($options)) . "')";
239 }
240 }
241 $this->query->orderBy("$column $dir");
a689294c
CW
242 }
243 }
244
245 /**
246 * @throws \CRM_Core_Exception
247 */
248 protected function buildLimit() {
249 if (!empty($this->limit) || !empty($this->offset)) {
f8bf8e26
CW
250 // If limit is 0, mysql will actually return 0 results. Instead set to maximum possible.
251 $this->query->limit($this->limit ?: '18446744073709551615', $this->offset);
19b53e5b
C
252 }
253 }
254
fba513f6 255 /**
3176b04c 256 * Adds GROUP BY clause to query
fba513f6
CW
257 */
258 protected function buildGroupBy() {
3176b04c 259 foreach ($this->groupBy as $item) {
a4499ec5 260 $this->query->groupBy($this->getExpression($item)->render($this->apiFieldSpec));
fba513f6
CW
261 }
262 }
263
19b53e5b
C
264 /**
265 * Recursively validate and transform a branch or leaf clause array to SQL.
266 *
267 * @param array $clause
c9e3ae2e 268 * @param string $type
16f5a13d 269 * WHERE|HAVING|ON
19b53e5b
C
270 * @return string SQL where clause
271 *
c9e3ae2e
CW
272 * @throws \API_Exception
273 * @uses composeClause() to generate the SQL etc.
19b53e5b 274 */
c9e3ae2e 275 protected function treeWalkClauses($clause, $type) {
0c9bccf1
CW
276 // Skip empty leaf.
277 if (in_array($clause[0], ['AND', 'OR', 'NOT']) && empty($clause[1])) {
278 return '';
279 }
19b53e5b
C
280 switch ($clause[0]) {
281 case 'OR':
282 case 'AND':
283 // handle branches
284 if (count($clause[1]) === 1) {
285 // a single set so AND|OR is immaterial
c9e3ae2e 286 return $this->treeWalkClauses($clause[1][0], $type);
19b53e5b
C
287 }
288 else {
289 $sql_subclauses = [];
290 foreach ($clause[1] as $subclause) {
c9e3ae2e 291 $sql_subclauses[] = $this->treeWalkClauses($subclause, $type);
19b53e5b
C
292 }
293 return '(' . implode("\n" . $clause[0], $sql_subclauses) . ')';
294 }
295
296 case 'NOT':
297 // If we get a group of clauses with no operator, assume AND
298 if (!is_string($clause[1][0])) {
299 $clause[1] = ['AND', $clause[1]];
300 }
c9e3ae2e 301 return 'NOT (' . $this->treeWalkClauses($clause[1], $type) . ')';
19b53e5b
C
302
303 default:
c9e3ae2e 304 return $this->composeClause($clause, $type);
19b53e5b
C
305 }
306 }
307
308 /**
309 * Validate and transform a leaf clause array to SQL.
310 * @param array $clause [$fieldName, $operator, $criteria]
c9e3ae2e 311 * @param string $type
16f5a13d 312 * WHERE|HAVING|ON
19b53e5b
C
313 * @return string SQL
314 * @throws \API_Exception
315 * @throws \Exception
316 */
c9e3ae2e 317 protected function composeClause(array $clause, string $type) {
19b53e5b 318 // Pad array for unary operators
c9e3ae2e 319 list($expr, $operator, $value) = array_pad($clause, 3, NULL);
16f5a13d
CW
320 if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
321 throw new \API_Exception('Illegal operator');
322 }
19b53e5b 323
c9e3ae2e
CW
324 // For WHERE clause, expr must be the name of a field.
325 if ($type === 'WHERE') {
326 $field = $this->getField($expr, TRUE);
37d82abe 327 FormattingUtil::formatInputValue($value, $expr, $field);
c9e3ae2e
CW
328 $fieldAlias = $field['sql_name'];
329 }
330 // For HAVING, expr must be an item in the SELECT clause
16f5a13d 331 elseif ($type === 'HAVING') {
37d82abe 332 // Expr references a fieldName or alias
c9e3ae2e
CW
333 if (isset($this->selectAliases[$expr])) {
334 $fieldAlias = $expr;
37d82abe
CW
335 // Attempt to format if this is a real field
336 if (isset($this->apiFieldSpec[$expr])) {
337 FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$expr]);
338 }
c9e3ae2e 339 }
37d82abe 340 // Expr references a non-field expression like a function; convert to alias
c9e3ae2e
CW
341 elseif (in_array($expr, $this->selectAliases)) {
342 $fieldAlias = array_search($expr, $this->selectAliases);
343 }
37d82abe 344 // If either the having or select field contains a pseudoconstant suffix, match and perform substitution
c9e3ae2e 345 else {
37d82abe
CW
346 list($fieldName) = explode(':', $expr);
347 foreach ($this->selectAliases as $selectAlias => $selectExpr) {
348 list($selectField) = explode(':', $selectAlias);
349 if ($selectAlias === $selectExpr && $fieldName === $selectField && isset($this->apiFieldSpec[$fieldName])) {
350 FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$fieldName]);
351 $fieldAlias = $selectAlias;
352 break;
353 }
354 }
355 }
356 if (!isset($fieldAlias)) {
357 throw new \API_Exception("Invalid expression in HAVING clause: '$expr'. Must use a value from SELECT clause.");
c9e3ae2e 358 }
37d82abe 359 $fieldAlias = '`' . $fieldAlias . '`';
c9e3ae2e 360 }
16f5a13d
CW
361 elseif ($type === 'ON') {
362 $expr = $this->getExpression($expr);
363 $fieldName = count($expr->getFields()) === 1 ? $expr->getFields()[0] : NULL;
364 $fieldAlias = $expr->render($this->apiFieldSpec);
365 if (is_string($value)) {
366 $valExpr = $this->getExpression($value);
367 if ($fieldName && $valExpr->getType() === 'SqlString') {
368 FormattingUtil::formatInputValue($valExpr->expr, $fieldName, $this->apiFieldSpec[$fieldName]);
369 }
370 return sprintf('%s %s %s', $fieldAlias, $operator, $valExpr->render($this->apiFieldSpec));
371 }
372 elseif ($fieldName) {
373 FormattingUtil::formatInputValue($value, $fieldName, $this->apiFieldSpec[$fieldName]);
374 }
375 }
19b53e5b 376
c9e3ae2e 377 $sql_clause = \CRM_Core_DAO::createSQLFilter($fieldAlias, [$operator => $value]);
19b53e5b 378 if ($sql_clause === NULL) {
c9e3ae2e 379 throw new \API_Exception("Invalid value in $type clause for '$expr'");
19b53e5b
C
380 }
381 return $sql_clause;
382 }
383
16f5a13d
CW
384 /**
385 * @param string $expr
386 * @return SqlExpression
387 * @throws \API_Exception
388 */
389 protected function getExpression(string $expr) {
390 $sqlExpr = SqlExpression::convert($expr);
391 foreach ($sqlExpr->getFields() as $fieldName) {
392 $this->getField($fieldName, TRUE);
393 }
394 return $sqlExpr;
395 }
396
19b53e5b
C
397 /**
398 * @inheritDoc
399 */
400 protected function getFields() {
401 return $this->apiFieldSpec;
402 }
403
404 /**
405 * Fetch a field from the getFields list
406 *
961e974c 407 * @param string $expr
a689294c 408 * @param bool $strict
3176b04c 409 * In strict mode, this will throw an exception if the field doesn't exist
19b53e5b
C
410 *
411 * @return string|null
a689294c 412 * @throws \API_Exception
19b53e5b 413 */
961e974c
CW
414 public function getField($expr, $strict = FALSE) {
415 // If the expression contains a pseudoconstant filter like activity_type_id:label,
416 // strip it to look up the base field name, then add the field:filter key to apiFieldSpec
417 $col = strpos($expr, ':');
418 $fieldName = $col ? substr($expr, 0, $col) : $expr;
a689294c
CW
419 // Perform join if field not yet available - this will add it to apiFieldSpec
420 if (!isset($this->apiFieldSpec[$fieldName]) && strpos($fieldName, '.')) {
334bebdd 421 $this->autoJoinFK($fieldName);
a689294c
CW
422 }
423 $field = $this->apiFieldSpec[$fieldName] ?? NULL;
3176b04c 424 if ($strict && !$field) {
a689294c 425 throw new \API_Exception("Invalid field '$fieldName'");
19b53e5b 426 }
961e974c 427 $this->apiFieldSpec[$expr] = $field;
3176b04c 428 return $field;
19b53e5b
C
429 }
430
16f5a13d
CW
431 /**
432 * Join onto other entities as specified by the api call.
433 *
434 * @param $joins
435 * @throws \API_Exception
436 * @throws \Civi\API\Exception\NotImplementedException
437 */
438 private function addExplicitJoins($joins) {
439 foreach ($joins as $join) {
440 // First item in the array is the entity name
441 $entity = array_shift($join);
442 // Which might contain an alias. Split on the keyword "AS"
443 list($entity, $alias) = array_pad(explode(' AS ', $entity), 2, NULL);
444 // Ensure alias is a safe string, and supply default if not given
445 $alias = $alias ? \CRM_Utils_String::munge($alias) : strtolower($entity);
446 // First item in the array is a boolean indicating if the join is required (aka INNER or LEFT).
447 // The rest are join conditions.
448 $side = array_shift($join) ? 'INNER' : 'LEFT';
449 $joinEntityGet = \Civi\API\Request::create($entity, 'get', ['version' => 4, 'checkPermissions' => $this->checkPermissions]);
450 foreach ($joinEntityGet->entityFields() as $field) {
451 $field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`';
452 $field['is_join'] = TRUE;
453 $this->addSpecField($alias . '.' . $field['name'], $field);
454 }
5e327f37
CW
455 $conditions = $this->getJoinConditions($entity, $alias);
456 foreach (array_filter($join) as $clause) {
16f5a13d
CW
457 $conditions[] = $this->treeWalkClauses($clause, 'ON');
458 }
5e327f37 459 $tableName = CoreUtil::getTableName($entity);
16f5a13d
CW
460 $this->join($side, $tableName, $alias, $conditions);
461 }
462 }
463
464 /**
465 * Supply conditions for an explicit join.
466 *
467 * @param $entity
468 * @param $alias
469 * @return array
470 */
471 private function getJoinConditions($entity, $alias) {
472 $conditions = [];
473 // getAclClause() expects a stack of 1-to-1 join fields to help it dedupe, but this is more flexible,
474 // so unless this is a direct 1-to-1 join with the main entity, we'll just hack it
475 // with a padded empty stack to bypass its deduping.
476 $stack = [NULL, NULL];
477 foreach ($this->apiFieldSpec as $name => $field) {
478 if ($field['entity'] !== $entity && $field['fk_entity'] === $entity) {
5e327f37 479 $conditions[] = $this->treeWalkClauses([$name, '=', "$alias.id"], 'ON');
16f5a13d
CW
480 }
481 elseif (strpos($name, "$alias.") === 0 && substr_count($name, '.') === 1 && $field['fk_entity'] === $this->entity) {
5e327f37
CW
482 $conditions[] = $this->treeWalkClauses([$name, '=', 'id'], 'ON');
483 $stack = ['id'];
16f5a13d
CW
484 }
485 }
486 // Hmm, if we came up with > 1 condition, then it's ambiguous how it should be joined so we won't return anything but the generic ACLs
487 if (count($conditions) > 1) {
5e327f37
CW
488 $stack = [NULL, NULL];
489 $conditions = [];
16f5a13d 490 }
5e327f37
CW
491 $baoName = CoreUtil::getBAOFromApiName($entity);
492 $acls = array_values($this->getAclClause($alias, $baoName, $stack));
16f5a13d
CW
493 return array_merge($acls, $conditions);
494 }
495
19b53e5b 496 /**
334bebdd 497 * Joins a path and adds all fields in the joined entity to apiFieldSpec
a689294c 498 *
19b53e5b
C
499 * @param $key
500 * @throws \API_Exception
a689294c 501 * @throws \Exception
19b53e5b 502 */
334bebdd 503 protected function autoJoinFK($key) {
a689294c 504 if (isset($this->apiFieldSpec[$key])) {
9b06167d 505 return;
19b53e5b
C
506 }
507
a689294c
CW
508 $pathArray = explode('.', $key);
509
19b53e5b
C
510 /** @var \Civi\Api4\Service\Schema\Joiner $joiner */
511 $joiner = \Civi::container()->get('joiner');
a689294c
CW
512 // The last item in the path is the field name. We don't care about that; we'll add all fields from the joined entity.
513 array_pop($pathArray);
19b53e5b
C
514 $pathString = implode('.', $pathArray);
515
334bebdd 516 if (!$joiner->canAutoJoin($this->getFrom(), $pathString)) {
9b06167d 517 return;
19b53e5b
C
518 }
519
520 $joinPath = $joiner->join($this, $pathString);
9b06167d 521
19b53e5b
C
522 $lastLink = array_pop($joinPath);
523
a689294c 524 // Custom field names are already prefixed
9b06167d
CW
525 $isCustom = $lastLink instanceof CustomGroupJoinable;
526 if ($isCustom) {
a689294c 527 array_pop($pathArray);
39e0f675 528 }
a689294c 529 $prefix = $pathArray ? implode('.', $pathArray) . '.' : '';
19b53e5b 530 // Cache field info for retrieval by $this->getField()
a689294c 531 foreach ($lastLink->getEntityFields() as $fieldObject) {
6fe7bdee 532 $fieldArray = $fieldObject->toArray();
a689294c 533 $fieldArray['sql_name'] = '`' . $lastLink->getAlias() . '`.`' . $fieldArray['column_name'] . '`';
9b06167d
CW
534 $fieldArray['is_custom'] = $isCustom;
535 $fieldArray['is_join'] = TRUE;
9b06167d 536 $this->addSpecField($prefix . $fieldArray['name'], $fieldArray);
19b53e5b 537 }
19b53e5b
C
538 }
539
19b53e5b
C
540 /**
541 * @return FALSE|string
542 */
543 public function getFrom() {
5e327f37 544 return CoreUtil::getTableName($this->entity);
19b53e5b
C
545 }
546
547 /**
548 * @return string
549 */
550 public function getEntity() {
551 return $this->entity;
552 }
553
554 /**
555 * @return array
556 */
557 public function getSelect() {
558 return $this->select;
559 }
560
561 /**
562 * @return array
563 */
564 public function getWhere() {
565 return $this->where;
566 }
567
568 /**
569 * @return array
570 */
571 public function getOrderBy() {
572 return $this->orderBy;
573 }
574
575 /**
576 * @return mixed
577 */
578 public function getLimit() {
579 return $this->limit;
580 }
581
582 /**
583 * @return mixed
584 */
585 public function getOffset() {
586 return $this->offset;
587 }
588
589 /**
590 * @return array
591 */
592 public function getSelectFields() {
593 return $this->selectFields;
594 }
595
19b53e5b
C
596 /**
597 * @return \CRM_Utils_SQL_Select
598 */
599 public function getQuery() {
600 return $this->query;
601 }
602
603 /**
604 * @return array
605 */
606 public function getJoins() {
607 return $this->joins;
608 }
609
610 /**
611 * @return array
612 */
613 public function getApiFieldSpec() {
614 return $this->apiFieldSpec;
615 }
616
617 /**
618 * @return array
619 */
620 public function getEntityFieldNames() {
621 return $this->entityFieldNames;
622 }
623
624 /**
625 * @return array
626 */
627 public function getAclFields() {
628 return $this->aclFields;
629 }
630
631 /**
632 * @return bool|string
633 */
634 public function getCheckPermissions() {
635 return $this->checkPermissions;
636 }
637
638 /**
639 * @return int
640 */
641 public function getApiVersion() {
642 return $this->apiVersion;
643 }
644
19b53e5b
C
645 /**
646 * Get table name on basis of entity
647 *
19b53e5b
C
648 * @return void
649 */
5e327f37
CW
650 public function constructQueryObject() {
651 $tableName = CoreUtil::getTableName($this->entity);
652 $this->query = \CRM_Utils_SQL_Select::from($tableName . ' ' . self::MAIN_TABLE_ALIAS);
19b53e5b
C
653 }
654
9b06167d
CW
655 /**
656 * @param $path
657 * @param $field
658 */
659 private function addSpecField($path, $field) {
660 // Only add field to spec if we have permission
661 if ($this->checkPermissions && !empty($field['permission']) && !\CRM_Core_Permission::check($field['permission'])) {
662 $this->apiFieldSpec[$path] = FALSE;
663 return;
664 }
665 $defaults = [];
334bebdd 666 $defaults['is_custom'] = $defaults['is_join'] = FALSE;
9b06167d
CW
667 $field += $defaults;
668 $this->apiFieldSpec[$path] = $field;
669 }
670
19b53e5b 671}