Elgg  Version 7.1
QueryBuilder.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
5 use Doctrine\DBAL\ArrayParameterType;
6 use Doctrine\DBAL\Connection;
7 use Doctrine\DBAL\ParameterType;
8 use Doctrine\DBAL\Query\Expression\CompositeExpression;
9 use Doctrine\DBAL\Query\QueryBuilder as DbalQueryBuilder;
10 use Doctrine\DBAL\Result;
16 use Elgg\Values;
17 
21 abstract class QueryBuilder extends DbalQueryBuilder {
22 
23  const CALCULATIONS = [
24  'avg',
25  'count',
26  'greatest',
27  'least',
28  'max',
29  'min',
30  'sum',
31  ];
32 
33  protected array $joins = [];
34 
35  protected int $join_index = 0;
36 
37  protected ?string $table_name = null;
38 
39  protected ?string $table_alias = null;
40 
46  public function __construct(protected readonly Connection $backup_connection) {
47  parent::__construct($backup_connection);
48  }
49 
57  public function getConnection(): Connection {
58  return $this->backup_connection;
59  }
60 
69  public function subquery(string $table, ?string $alias = null): Select {
70  $qb = new Select($this->getConnection());
71  $qb->from($table, $alias);
72 
73  return $qb;
74  }
75 
84  public function addClause(Clause $clause, ?string $alias = null): static {
85  if (!isset($alias)) {
86  $alias = $this->getTableAlias();
87  }
88 
89  $expr = $clause->prepare($this, $alias);
90  if ($clause instanceof WhereClause && ($expr instanceof CompositeExpression || is_string($expr))) {
91  $this->andWhere($expr);
92  }
93 
94  return $this;
95  }
96 
104  public function prefix(string $table): string {
105  $prefix = _elgg_services()->db->prefix;
106  if ($prefix === '') {
107  return $table;
108  }
109 
110  if (!str_starts_with($table, $prefix)) {
111  return "{$prefix}{$table}";
112  }
113 
114  return $table;
115  }
116 
122  public function getTableName(): string {
123  return (string) $this->table_name;
124  }
125 
131  public function getTableAlias(): ?string {
132  return $this->table_alias;
133  }
134 
145  public function param($value, string $type = ELGG_VALUE_STRING, ?string $key = null): string {
146  if (!$key) {
147  $parameters = $this->getParameters();
148  $key = ':qb' . (count($parameters) + 1);
149  }
150 
151  switch ($type) {
152  case ELGG_VALUE_GUID:
153  $value = Values::normalizeGuids($value);
154  $type = ParameterType::INTEGER;
155 
156  break;
157  case ELGG_VALUE_ID:
158  $value = Values::normalizeIds($value);
159  $type = ParameterType::INTEGER;
160 
161  break;
162  case ELGG_VALUE_INTEGER:
163  $type = ParameterType::INTEGER;
164 
165  break;
166  case ELGG_VALUE_BOOL:
167  $type = ParameterType::INTEGER;
168  $value = (int) $value;
169 
170  break;
171  case ELGG_VALUE_STRING:
172  $type = ParameterType::STRING;
173 
174  break;
176  $value = Values::normalizeTimestamp($value);
177  $type = ParameterType::INTEGER;
178 
179  break;
180  }
181 
182  // convert array value or type based on array
183  if (is_array($value)) {
184  if (count($value) === 1) {
185  $value = array_shift($value);
186  } else {
187  if ($type === ParameterType::INTEGER) {
188  $type = ArrayParameterType::INTEGER;
189  } elseif ($type === ParameterType::STRING) {
190  $type = ArrayParameterType::STRING;
191  }
192  }
193  }
194 
195  return $this->createNamedParameter($value, $type, $key);
196  }
197 
205  public function execute(bool $track_query = true) {
206  if (!$track_query) {
207  if ($this instanceof Select) {
208  return $this->executeQuery();
209  } else {
210  return $this->executeStatement();
211  }
212  }
213 
214  return _elgg_services()->db->trackQuery($this, function() {
215  if ($this instanceof Select) {
216  return $this->executeQuery();
217  } else {
218  return $this->executeStatement();
219  }
220  });
221  }
222 
226  public function executeStatement(): int|string {
227  _elgg_services()->queryCache->clear();
228 
229  return parent::executeStatement();
230  }
231 
237  public function from(string $table, ?string $alias = null): self {
238  $this->table_name = $table;
239  $this->table_alias = $alias;
240 
241  return parent::from($this->prefix($table), $alias);
242  }
243 
249  public function insert(string $table): self {
250  $this->table_name = $table;
251 
252  return parent::insert($this->prefix($table));
253  }
254 
260  public function update(string $table): self {
261  $this->table_name = $table;
262 
263  return parent::update($this->prefix($table));
264  }
265 
271  public function delete(string $table): self {
272  $this->table_name = $table;
273 
274  return parent::delete($this->prefix($table));
275  }
276 
280  public function join(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
281  return parent::join($fromAlias, $this->prefix($join), $alias, $condition);
282  }
283 
287  public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
288  return parent::innerJoin($fromAlias, $this->prefix($join), $alias, $condition);
289  }
290 
294  public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
295  return parent::leftJoin($fromAlias, $this->prefix($join), $alias, $condition);
296  }
297 
301  public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
302  return parent::rightJoin($fromAlias, $this->prefix($join), $alias, $condition);
303  }
304 
308  public function orderBy(string $sort, ?string $order = null): self {
309  if (isset($order) && !in_array(strtoupper($order), ['ASC', 'DESC'])) {
310  throw new DomainException("'{$order}' is not a valid order by direction");
311  }
312 
313  return parent::orderBy($sort, $order);
314  }
315 
319  public function addOrderBy(string $sort, ?string $order = null): self {
320  if (isset($order) && !in_array(strtoupper($order), ['ASC', 'DESC'])) {
321  throw new DomainException("'{$order}' is not a valid order by direction");
322  }
323 
324  return parent::addOrderBy($sort, $order);
325  }
326 
335  public function merge($parts = null, $boolean = 'AND') {
336  if (empty($parts)) {
337  return null;
338  }
339 
340  $parts = (array) $parts;
341 
342  $parts = array_filter($parts, function ($e) {
343  if (empty($e)) {
344  return false;
345  }
346 
347  if (!$e instanceof CompositeExpression && !is_string($e)) {
348  return false;
349  }
350 
351  return true;
352  });
353  if (empty($parts)) {
354  return null;
355  }
356 
357  if (count($parts) === 1) {
358  return array_shift($parts);
359  }
360 
361  // PHP 8 can use named arguments in call_user_func_array(), this causes issues
362  // @see: https://www.php.net/manual/en/function.call-user-func-array.php#125953
363  $parts = array_values($parts);
364  if (strtoupper($boolean) === 'OR') {
365  return call_user_func_array([$this->expr(), 'or'], $parts);
366  }
367 
368  return call_user_func_array([$this->expr(), 'and'], $parts);
369  }
370 
387  public function compare(string $x, string $comparison, $y = null, ?string $type = null, ?bool $case_sensitive = null) {
388  return (new ComparisonClause($x, $comparison, $y, $type, $case_sensitive))->prepare($this);
389  }
390 
401  public function between(string $x, $lower = null, $upper = null, ?string $type = null) {
402  $wheres = [];
403  if ($lower) {
404  $wheres[] = $this->compare($x, '>=', $lower, $type);
405  }
406 
407  if ($upper) {
408  $wheres[] = $this->compare($x, '<=', $upper, $type);
409  }
410 
411  return $this->merge($wheres);
412  }
413 
419  public function getNextJoinAlias(): string {
420  $this->join_index++;
421 
422  return "qbt{$this->join_index}";
423  }
424 
435  public function joinEntitiesTable(string $from_alias = '', string $from_column = 'guid', ?string $join_type = 'inner', ?string $joined_alias = null): string {
436  if (in_array($joined_alias, $this->joins)) {
437  return $joined_alias;
438  }
439 
440  if ($from_alias) {
441  $from_column = "{$from_alias}.{$from_column}";
442  }
443 
444  $hash = sha1(serialize([
445  $join_type,
446  EntityTable::TABLE_NAME,
447  $from_column,
448  ]));
449 
450  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
451  return $this->joins[$hash];
452  }
453 
454  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column) {
455  return $qb->compare("{$joined_alias}.guid", '=', $from_column);
456  };
457 
458  $clause = new JoinClause(EntityTable::TABLE_NAME, $joined_alias, $condition, $join_type);
459  $joined_alias = $clause->prepare($this, $from_alias);
460 
461  $this->joins[$hash] = $joined_alias;
462 
463  return $joined_alias;
464  }
465 
477  public function joinMetadataTable(string $from_alias = '', string $from_column = 'guid', $name = null, ?string $join_type = 'inner', ?string $joined_alias = null): string {
478  if (in_array($joined_alias, $this->joins)) {
479  return $joined_alias;
480  }
481 
482  if ($from_alias) {
483  $from_column = "{$from_alias}.{$from_column}";
484  }
485 
486  $hash = sha1(serialize([
487  $join_type,
488  MetadataTable::TABLE_NAME,
489  $from_column,
490  (array) $name,
491  ]));
492 
493  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
494  return $this->joins[$hash];
495  }
496 
497  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name) {
498  return $qb->merge([
499  $qb->compare("{$joined_alias}.entity_guid", '=', $from_column),
500  $qb->compare("{$joined_alias}.name", '=', $name, ELGG_VALUE_STRING),
501  ]);
502  };
503 
504  $clause = new JoinClause(MetadataTable::TABLE_NAME, $joined_alias, $condition, $join_type);
505 
506  $joined_alias = $clause->prepare($this, $from_alias);
507 
508  $this->joins[$hash] = $joined_alias;
509 
510  return $joined_alias;
511  }
512 
524  public function joinAnnotationTable(string $from_alias = '', string $from_column = 'guid', $name = null, ?string $join_type = 'inner', ?string $joined_alias = null): string {
525  if (in_array($joined_alias, $this->joins)) {
526  return $joined_alias;
527  }
528 
529  if ($from_alias) {
530  $from_column = "{$from_alias}.{$from_column}";
531  }
532 
533  $hash = sha1(serialize([
534  $join_type,
535  AnnotationsTable::TABLE_NAME,
536  $from_column,
537  (array) $name,
538  ]));
539 
540  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
541  return $this->joins[$hash];
542  }
543 
544  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name) {
545  return $qb->merge([
546  $qb->compare("{$joined_alias}.entity_guid", '=', $from_column),
547  $qb->compare("{$joined_alias}.name", '=', $name, ELGG_VALUE_STRING),
548  ]);
549  };
550 
551  $clause = new JoinClause(AnnotationsTable::TABLE_NAME, $joined_alias, $condition, $join_type);
552 
553  $joined_alias = $clause->prepare($this, $from_alias);
554 
555  $this->joins[$hash] = $joined_alias;
556 
557  return $joined_alias;
558  }
559 
572  public function joinRelationshipTable(string $from_alias = '', string $from_column = 'guid', $name = null, bool $inverse = false, ?string $join_type = 'inner', ?string $joined_alias = null): string {
573  if (in_array($joined_alias, $this->joins)) {
574  return $joined_alias;
575  }
576 
577  if ($from_alias) {
578  $from_column = "{$from_alias}.{$from_column}";
579  }
580 
581  $hash = sha1(serialize([
582  $join_type,
583  RelationshipsTable::TABLE_NAME,
584  $from_column,
585  $inverse,
586  (array) $name,
587  ]));
588 
589  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
590  return $this->joins[$hash];
591  }
592 
593  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name, $inverse) {
594  $parts = [];
595  if ($inverse) {
596  $parts[] = $qb->compare("{$joined_alias}.guid_one", '=', $from_column);
597  } else {
598  $parts[] = $qb->compare("{$joined_alias}.guid_two", '=', $from_column);
599  }
600 
601  $parts[] = $qb->compare("{$joined_alias}.relationship", '=', $name, ELGG_VALUE_STRING);
602  return $qb->merge($parts);
603  };
604 
605  $clause = new JoinClause(RelationshipsTable::TABLE_NAME, $joined_alias, $condition, $join_type);
606 
607  $joined_alias = $clause->prepare($this, $from_alias);
608 
609  $this->joins[$hash] = $joined_alias;
610 
611  return $joined_alias;
612  }
613 }
if(! $user||! $user->canDelete()) $name
Definition: delete.php:22
$type
Definition: delete.php:21
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/clear'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin', 'controller'=> \Elgg\Diagnostics\DownloadController::class,], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:76
Interface that allows resolving statements and/or extending query builder.
Definition: Clause.php:14
Utility class for building composite comparison expression.
Extends QueryBuilder with JOIN clauses.
Definition: JoinClause.php:11
Builds a clause from closure or composite expression.
Definition: WhereClause.php:11
Database abstraction query builder.
join(string $fromAlias, string $join, string $alias, ?string $condition=null)
{}
merge($parts=null, $boolean='AND')
Merges multiple composite expressions with a boolean.
joinMetadataTable(string $from_alias='', string $from_column='guid', $name=null, ?string $join_type='inner', ?string $joined_alias=null)
Join metadata table from alias and return joined table alias.
between(string $x, $lower=null, $upper=null, ?string $type=null)
Build a between clause.
getNextJoinAlias()
Get an index of the next available join alias.
joinAnnotationTable(string $from_alias='', string $from_column='guid', $name=null, ?string $join_type='inner', ?string $joined_alias=null)
Join annotations table from alias and return joined table alias.
rightJoin(string $fromAlias, string $join, string $alias, ?string $condition=null)
{}
compare(string $x, string $comparison, $y=null, ?string $type=null, ?bool $case_sensitive=null)
Build value comparison clause.
prefix(string $table)
Prefixes the table name with installation DB prefix.
leftJoin(string $fromAlias, string $join, string $alias, ?string $condition=null)
{}
subquery(string $table, ?string $alias=null)
Creates a new SelectQueryBuilder for join/where sub queries using the DB connection of the primary Qu...
joinEntitiesTable(string $from_alias='', string $from_column='guid', ?string $join_type='inner', ?string $joined_alias=null)
Join entity table from alias and return joined table alias.
param($value, string $type=ELGG_VALUE_STRING, ?string $key=null)
Sets a new parameter assigning it a unique parameter key/name if none provided Returns the name of th...
addOrderBy(string $sort, ?string $order=null)
{}
getConnection()
Returns the connection.
getTableAlias()
Returns the alias of the primary table.
from(string $table, ?string $alias=null)
{}
orderBy(string $sort, ?string $order=null)
{}
innerJoin(string $fromAlias, string $join, string $alias, ?string $condition=null)
{}
execute(bool $track_query=true)
Execute the database query for this QueryBuilder.
addClause(Clause $clause, ?string $alias=null)
Apply clause to this instance.
getTableName()
Returns the name of the primary table.
__construct(protected readonly Connection $backup_connection)
Initializes a new QueryBuilder.
joinRelationshipTable(string $from_alias='', string $from_column='guid', $name=null, bool $inverse=false, ?string $join_type='inner', ?string $joined_alias=null)
Join relationship table from alias and return joined table alias.
Query builder for fetching data from the database.
Definition: Select.php:8
getConnection(string $type)
Gets (if required, also creates) a DB connection.
Definition: Database.php:113
Exception thrown if a value does not adhere to a defined valid data domain.
Functions for use as event handlers or other situations where you need a globally accessible callable...
Definition: Values.php:12
const ELGG_VALUE_BOOL
Definition: constants.php:116
const ELGG_VALUE_STRING
Definition: constants.php:112
const ELGG_VALUE_ID
Definition: constants.php:114
const ELGG_VALUE_GUID
Definition: constants.php:113
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:115
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:111
$table
Definition: database.php:52
if($item instanceof \ElggEntity) elseif($item instanceof \ElggRiverItem) elseif($item instanceof \ElggRelationship) elseif(is_callable([ $item, 'getType']))
Definition: item.php:48
_elgg_services()
Get the global service provider.
Definition: elgglib.php:347
$value
Definition: generic.php:51
$qb
Definition: queue.php:14
if($container instanceof ElggGroup && $container->guid !=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10