Merge pull request #17328 from eileenmcnaughton/mailing_seach
[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) {
c9e3ae2e
CW
202 $this->query->where($this->treeWalkClauses($clause, 'WHERE'));
203 }
204 }
205
206 /**
207 * Build HAVING clause.
208 *
209 * Every expression referenced must also be in the SELECT clause.
210 */
211 protected function buildHavingClause() {
212 foreach ($this->having as $clause) {
213 $this->query->having($this->treeWalkClauses($clause, 'HAVING'));
19b53e5b
C
214 }
215 }
216
217 /**
218 * @inheritDoc
219 */
220 protected function buildOrderBy() {
3176b04c 221 foreach ($this->orderBy as $item => $dir) {
19b53e5b 222 if ($dir !== 'ASC' && $dir !== 'DESC') {
3176b04c
CW
223 throw new \API_Exception("Invalid sort direction. Cannot order by $item $dir");
224 }
16f5a13d 225 $this->query->orderBy($this->renderExpression($item) . " $dir");
a689294c
CW
226 }
227 }
228
229 /**
230 * @throws \CRM_Core_Exception
231 */
232 protected function buildLimit() {
233 if (!empty($this->limit) || !empty($this->offset)) {
f8bf8e26
CW
234 // If limit is 0, mysql will actually return 0 results. Instead set to maximum possible.
235 $this->query->limit($this->limit ?: '18446744073709551615', $this->offset);
19b53e5b
C
236 }
237 }
238
fba513f6 239 /**
3176b04c 240 * Adds GROUP BY clause to query
fba513f6
CW
241 */
242 protected function buildGroupBy() {
3176b04c 243 foreach ($this->groupBy as $item) {
16f5a13d 244 $this->query->groupBy($this->renderExpression($item));
fba513f6
CW
245 }
246 }
247
19b53e5b
C
248 /**
249 * Recursively validate and transform a branch or leaf clause array to SQL.
250 *
251 * @param array $clause
c9e3ae2e 252 * @param string $type
16f5a13d 253 * WHERE|HAVING|ON
19b53e5b
C
254 * @return string SQL where clause
255 *
c9e3ae2e
CW
256 * @throws \API_Exception
257 * @uses composeClause() to generate the SQL etc.
19b53e5b 258 */
c9e3ae2e 259 protected function treeWalkClauses($clause, $type) {
19b53e5b
C
260 switch ($clause[0]) {
261 case 'OR':
262 case 'AND':
263 // handle branches
264 if (count($clause[1]) === 1) {
265 // a single set so AND|OR is immaterial
c9e3ae2e 266 return $this->treeWalkClauses($clause[1][0], $type);
19b53e5b
C
267 }
268 else {
269 $sql_subclauses = [];
270 foreach ($clause[1] as $subclause) {
c9e3ae2e 271 $sql_subclauses[] = $this->treeWalkClauses($subclause, $type);
19b53e5b
C
272 }
273 return '(' . implode("\n" . $clause[0], $sql_subclauses) . ')';
274 }
275
276 case 'NOT':
277 // If we get a group of clauses with no operator, assume AND
278 if (!is_string($clause[1][0])) {
279 $clause[1] = ['AND', $clause[1]];
280 }
c9e3ae2e 281 return 'NOT (' . $this->treeWalkClauses($clause[1], $type) . ')';
19b53e5b
C
282
283 default:
c9e3ae2e 284 return $this->composeClause($clause, $type);
19b53e5b
C
285 }
286 }
287
288 /**
289 * Validate and transform a leaf clause array to SQL.
290 * @param array $clause [$fieldName, $operator, $criteria]
c9e3ae2e 291 * @param string $type
16f5a13d 292 * WHERE|HAVING|ON
19b53e5b
C
293 * @return string SQL
294 * @throws \API_Exception
295 * @throws \Exception
296 */
c9e3ae2e 297 protected function composeClause(array $clause, string $type) {
19b53e5b 298 // Pad array for unary operators
c9e3ae2e 299 list($expr, $operator, $value) = array_pad($clause, 3, NULL);
16f5a13d
CW
300 if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
301 throw new \API_Exception('Illegal operator');
302 }
19b53e5b 303
c9e3ae2e
CW
304 // For WHERE clause, expr must be the name of a field.
305 if ($type === 'WHERE') {
306 $field = $this->getField($expr, TRUE);
37d82abe 307 FormattingUtil::formatInputValue($value, $expr, $field);
c9e3ae2e
CW
308 $fieldAlias = $field['sql_name'];
309 }
310 // For HAVING, expr must be an item in the SELECT clause
16f5a13d 311 elseif ($type === 'HAVING') {
37d82abe 312 // Expr references a fieldName or alias
c9e3ae2e
CW
313 if (isset($this->selectAliases[$expr])) {
314 $fieldAlias = $expr;
37d82abe
CW
315 // Attempt to format if this is a real field
316 if (isset($this->apiFieldSpec[$expr])) {
317 FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$expr]);
318 }
c9e3ae2e 319 }
37d82abe 320 // Expr references a non-field expression like a function; convert to alias
c9e3ae2e
CW
321 elseif (in_array($expr, $this->selectAliases)) {
322 $fieldAlias = array_search($expr, $this->selectAliases);
323 }
37d82abe 324 // If either the having or select field contains a pseudoconstant suffix, match and perform substitution
c9e3ae2e 325 else {
37d82abe
CW
326 list($fieldName) = explode(':', $expr);
327 foreach ($this->selectAliases as $selectAlias => $selectExpr) {
328 list($selectField) = explode(':', $selectAlias);
329 if ($selectAlias === $selectExpr && $fieldName === $selectField && isset($this->apiFieldSpec[$fieldName])) {
330 FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$fieldName]);
331 $fieldAlias = $selectAlias;
332 break;
333 }
334 }
335 }
336 if (!isset($fieldAlias)) {
337 throw new \API_Exception("Invalid expression in HAVING clause: '$expr'. Must use a value from SELECT clause.");
c9e3ae2e 338 }
37d82abe 339 $fieldAlias = '`' . $fieldAlias . '`';
c9e3ae2e 340 }
16f5a13d
CW
341 elseif ($type === 'ON') {
342 $expr = $this->getExpression($expr);
343 $fieldName = count($expr->getFields()) === 1 ? $expr->getFields()[0] : NULL;
344 $fieldAlias = $expr->render($this->apiFieldSpec);
345 if (is_string($value)) {
346 $valExpr = $this->getExpression($value);
347 if ($fieldName && $valExpr->getType() === 'SqlString') {
348 FormattingUtil::formatInputValue($valExpr->expr, $fieldName, $this->apiFieldSpec[$fieldName]);
349 }
350 return sprintf('%s %s %s', $fieldAlias, $operator, $valExpr->render($this->apiFieldSpec));
351 }
352 elseif ($fieldName) {
353 FormattingUtil::formatInputValue($value, $fieldName, $this->apiFieldSpec[$fieldName]);
354 }
355 }
19b53e5b 356
c9e3ae2e 357 $sql_clause = \CRM_Core_DAO::createSQLFilter($fieldAlias, [$operator => $value]);
19b53e5b 358 if ($sql_clause === NULL) {
c9e3ae2e 359 throw new \API_Exception("Invalid value in $type clause for '$expr'");
19b53e5b
C
360 }
361 return $sql_clause;
362 }
363
16f5a13d
CW
364 /**
365 * @param string $expr
366 * @return SqlExpression
367 * @throws \API_Exception
368 */
369 protected function getExpression(string $expr) {
370 $sqlExpr = SqlExpression::convert($expr);
371 foreach ($sqlExpr->getFields() as $fieldName) {
372 $this->getField($fieldName, TRUE);
373 }
374 return $sqlExpr;
375 }
376
377 /**
378 * @param string $expr
379 * @return string
380 * @throws \API_Exception
381 */
382 protected function renderExpression(string $expr) {
383 $sqlExpr = $this->getExpression($expr);
384 return $sqlExpr->render($this->apiFieldSpec);
385 }
386
19b53e5b
C
387 /**
388 * @inheritDoc
389 */
390 protected function getFields() {
391 return $this->apiFieldSpec;
392 }
393
394 /**
395 * Fetch a field from the getFields list
396 *
961e974c 397 * @param string $expr
a689294c 398 * @param bool $strict
3176b04c 399 * In strict mode, this will throw an exception if the field doesn't exist
19b53e5b
C
400 *
401 * @return string|null
a689294c 402 * @throws \API_Exception
19b53e5b 403 */
961e974c
CW
404 public function getField($expr, $strict = FALSE) {
405 // If the expression contains a pseudoconstant filter like activity_type_id:label,
406 // strip it to look up the base field name, then add the field:filter key to apiFieldSpec
407 $col = strpos($expr, ':');
408 $fieldName = $col ? substr($expr, 0, $col) : $expr;
a689294c
CW
409 // Perform join if field not yet available - this will add it to apiFieldSpec
410 if (!isset($this->apiFieldSpec[$fieldName]) && strpos($fieldName, '.')) {
334bebdd 411 $this->autoJoinFK($fieldName);
a689294c
CW
412 }
413 $field = $this->apiFieldSpec[$fieldName] ?? NULL;
3176b04c 414 if ($strict && !$field) {
a689294c 415 throw new \API_Exception("Invalid field '$fieldName'");
19b53e5b 416 }
961e974c 417 $this->apiFieldSpec[$expr] = $field;
3176b04c 418 return $field;
19b53e5b
C
419 }
420
16f5a13d
CW
421 /**
422 * Join onto other entities as specified by the api call.
423 *
424 * @param $joins
425 * @throws \API_Exception
426 * @throws \Civi\API\Exception\NotImplementedException
427 */
428 private function addExplicitJoins($joins) {
429 foreach ($joins as $join) {
430 // First item in the array is the entity name
431 $entity = array_shift($join);
432 // Which might contain an alias. Split on the keyword "AS"
433 list($entity, $alias) = array_pad(explode(' AS ', $entity), 2, NULL);
434 // Ensure alias is a safe string, and supply default if not given
435 $alias = $alias ? \CRM_Utils_String::munge($alias) : strtolower($entity);
436 // First item in the array is a boolean indicating if the join is required (aka INNER or LEFT).
437 // The rest are join conditions.
438 $side = array_shift($join) ? 'INNER' : 'LEFT';
439 $joinEntityGet = \Civi\API\Request::create($entity, 'get', ['version' => 4, 'checkPermissions' => $this->checkPermissions]);
440 foreach ($joinEntityGet->entityFields() as $field) {
441 $field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`';
442 $field['is_join'] = TRUE;
443 $this->addSpecField($alias . '.' . $field['name'], $field);
444 }
5e327f37
CW
445 $conditions = $this->getJoinConditions($entity, $alias);
446 foreach (array_filter($join) as $clause) {
16f5a13d
CW
447 $conditions[] = $this->treeWalkClauses($clause, 'ON');
448 }
5e327f37 449 $tableName = CoreUtil::getTableName($entity);
16f5a13d
CW
450 $this->join($side, $tableName, $alias, $conditions);
451 }
452 }
453
454 /**
455 * Supply conditions for an explicit join.
456 *
457 * @param $entity
458 * @param $alias
459 * @return array
460 */
461 private function getJoinConditions($entity, $alias) {
462 $conditions = [];
463 // getAclClause() expects a stack of 1-to-1 join fields to help it dedupe, but this is more flexible,
464 // so unless this is a direct 1-to-1 join with the main entity, we'll just hack it
465 // with a padded empty stack to bypass its deduping.
466 $stack = [NULL, NULL];
467 foreach ($this->apiFieldSpec as $name => $field) {
468 if ($field['entity'] !== $entity && $field['fk_entity'] === $entity) {
5e327f37 469 $conditions[] = $this->treeWalkClauses([$name, '=', "$alias.id"], 'ON');
16f5a13d
CW
470 }
471 elseif (strpos($name, "$alias.") === 0 && substr_count($name, '.') === 1 && $field['fk_entity'] === $this->entity) {
5e327f37
CW
472 $conditions[] = $this->treeWalkClauses([$name, '=', 'id'], 'ON');
473 $stack = ['id'];
16f5a13d
CW
474 }
475 }
476 // 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
477 if (count($conditions) > 1) {
5e327f37
CW
478 $stack = [NULL, NULL];
479 $conditions = [];
16f5a13d 480 }
5e327f37
CW
481 $baoName = CoreUtil::getBAOFromApiName($entity);
482 $acls = array_values($this->getAclClause($alias, $baoName, $stack));
16f5a13d
CW
483 return array_merge($acls, $conditions);
484 }
485
19b53e5b 486 /**
334bebdd 487 * Joins a path and adds all fields in the joined entity to apiFieldSpec
a689294c 488 *
19b53e5b
C
489 * @param $key
490 * @throws \API_Exception
a689294c 491 * @throws \Exception
19b53e5b 492 */
334bebdd 493 protected function autoJoinFK($key) {
a689294c 494 if (isset($this->apiFieldSpec[$key])) {
9b06167d 495 return;
19b53e5b
C
496 }
497
a689294c
CW
498 $pathArray = explode('.', $key);
499
19b53e5b
C
500 /** @var \Civi\Api4\Service\Schema\Joiner $joiner */
501 $joiner = \Civi::container()->get('joiner');
a689294c
CW
502 // 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.
503 array_pop($pathArray);
19b53e5b
C
504 $pathString = implode('.', $pathArray);
505
334bebdd 506 if (!$joiner->canAutoJoin($this->getFrom(), $pathString)) {
9b06167d 507 return;
19b53e5b
C
508 }
509
510 $joinPath = $joiner->join($this, $pathString);
9b06167d 511
19b53e5b
C
512 $lastLink = array_pop($joinPath);
513
a689294c 514 // Custom field names are already prefixed
9b06167d
CW
515 $isCustom = $lastLink instanceof CustomGroupJoinable;
516 if ($isCustom) {
a689294c 517 array_pop($pathArray);
39e0f675 518 }
a689294c 519 $prefix = $pathArray ? implode('.', $pathArray) . '.' : '';
19b53e5b 520 // Cache field info for retrieval by $this->getField()
a689294c
CW
521 $joinEntity = $lastLink->getEntity();
522 foreach ($lastLink->getEntityFields() as $fieldObject) {
523 $fieldArray = ['entity' => $joinEntity] + $fieldObject->toArray();
524 $fieldArray['sql_name'] = '`' . $lastLink->getAlias() . '`.`' . $fieldArray['column_name'] . '`';
9b06167d
CW
525 $fieldArray['is_custom'] = $isCustom;
526 $fieldArray['is_join'] = TRUE;
9b06167d 527 $this->addSpecField($prefix . $fieldArray['name'], $fieldArray);
19b53e5b 528 }
19b53e5b
C
529 }
530
19b53e5b
C
531 /**
532 * @return FALSE|string
533 */
534 public function getFrom() {
5e327f37 535 return CoreUtil::getTableName($this->entity);
19b53e5b
C
536 }
537
538 /**
539 * @return string
540 */
541 public function getEntity() {
542 return $this->entity;
543 }
544
545 /**
546 * @return array
547 */
548 public function getSelect() {
549 return $this->select;
550 }
551
552 /**
553 * @return array
554 */
555 public function getWhere() {
556 return $this->where;
557 }
558
559 /**
560 * @return array
561 */
562 public function getOrderBy() {
563 return $this->orderBy;
564 }
565
566 /**
567 * @return mixed
568 */
569 public function getLimit() {
570 return $this->limit;
571 }
572
573 /**
574 * @return mixed
575 */
576 public function getOffset() {
577 return $this->offset;
578 }
579
580 /**
581 * @return array
582 */
583 public function getSelectFields() {
584 return $this->selectFields;
585 }
586
19b53e5b
C
587 /**
588 * @return \CRM_Utils_SQL_Select
589 */
590 public function getQuery() {
591 return $this->query;
592 }
593
594 /**
595 * @return array
596 */
597 public function getJoins() {
598 return $this->joins;
599 }
600
601 /**
602 * @return array
603 */
604 public function getApiFieldSpec() {
605 return $this->apiFieldSpec;
606 }
607
608 /**
609 * @return array
610 */
611 public function getEntityFieldNames() {
612 return $this->entityFieldNames;
613 }
614
615 /**
616 * @return array
617 */
618 public function getAclFields() {
619 return $this->aclFields;
620 }
621
622 /**
623 * @return bool|string
624 */
625 public function getCheckPermissions() {
626 return $this->checkPermissions;
627 }
628
629 /**
630 * @return int
631 */
632 public function getApiVersion() {
633 return $this->apiVersion;
634 }
635
19b53e5b
C
636 /**
637 * Get table name on basis of entity
638 *
19b53e5b
C
639 * @return void
640 */
5e327f37
CW
641 public function constructQueryObject() {
642 $tableName = CoreUtil::getTableName($this->entity);
643 $this->query = \CRM_Utils_SQL_Select::from($tableName . ' ' . self::MAIN_TABLE_ALIAS);
19b53e5b
C
644 }
645
9b06167d
CW
646 /**
647 * @param $path
648 * @param $field
649 */
650 private function addSpecField($path, $field) {
651 // Only add field to spec if we have permission
652 if ($this->checkPermissions && !empty($field['permission']) && !\CRM_Core_Permission::check($field['permission'])) {
653 $this->apiFieldSpec[$path] = FALSE;
654 return;
655 }
656 $defaults = [];
334bebdd 657 $defaults['is_custom'] = $defaults['is_join'] = FALSE;
9b06167d
CW
658 $field += $defaults;
659 $this->apiFieldSpec[$path] = $field;
660 }
661
19b53e5b 662}