Elgg  Version 5.1
River.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
12 
18 class River extends Repository {
19 
23  public function __construct(array $options = []) {
24  $singulars = [
25  'id',
26  'subject_guid',
27  'object_guid',
28  'target_guid',
29  'annotation_id',
30  'action_type',
31  'type',
32  'subtype',
33  'view',
34  ];
35 
36  $options = QueryOptions::normalizePluralOptions($options, $singulars);
37 
38  $defaults = [
39  'ids' => null,
40  'subject_guids' => null,
41  'object_guids' => null,
42  'target_guids' => null,
43  'annotation_ids' => null,
44  'views' => null,
45  'action_types' => null,
46  'posted_time_lower' => null,
47  'posted_time_upper' => null,
48  'limit' => 20,
49  'offset' => 0,
50  ];
51 
52  $options = array_merge($defaults, $options);
53 
54  // prevent conflicts with annotation ids for annotation where clause
55  $options['river_annotation_ids'] = elgg_extract('river_annotation_ids', $options, $options['annotation_ids']);
56  unset($options['annotation_ids']);
57 
58  parent::__construct($options);
59  }
60 
68  public static function find(array $options = []) {
69  return parent::find($options);
70  }
71 
75  public function count() {
76  $qb = Select::fromTable('river', 'rv');
77 
78  $count_expr = $this->options->distinct ? 'DISTINCT rv.id' : '*';
79  $qb->select("COUNT({$count_expr}) AS total");
80 
81  $qb = $this->buildQuery($qb);
82 
83  $result = _elgg_services()->db->getDataRow($qb);
84 
85  return $result ? (int) $result->total : 0;
86  }
87 
98  public function calculate($function, $property, $property_type = 'annotation') {
99  if (!in_array(strtolower($function), QueryBuilder::CALCULATIONS)) {
100  throw new DomainException("'{$function}' is not a valid numeric function");
101  }
102 
103  $qb = Select::fromTable('river', 'rv');
104 
105  $alias = 'n_table';
106  if (!empty($this->options->annotation_name_value_pairs) && $this->options->annotation_name_value_pairs[0]->names != $property) {
107  $alias = $qb->getNextJoinAlias();
108 
110  $annotation->names = $property;
111  $qb->addClause($annotation, $alias);
112  }
113 
114  $qb->join('rv', 'annotations', $alias, "rv.annotation_id = {$alias}.id");
115  $qb->select("{$function}(n_table.value) AS calculation");
116 
117  $qb = $this->buildQuery($qb);
118 
119  $result = _elgg_services()->db->getDataRow($qb);
120 
121  return $result ? (int) $result->calculation : 0;
122  }
123 
133  public function get($limit = null, $offset = null, $callback = null) {
134  $qb = Select::fromTable('river', 'rv');
135 
136  $distinct = $this->options->distinct ? 'DISTINCT' : '';
137  $qb->select("{$distinct} rv.*");
138 
139  $this->expandInto($qb, 'rv');
140 
141  $qb = $this->buildQuery($qb);
142 
143  // Keeping things backwards compatible
144  $original_order = elgg_extract('order_by', $this->options->__original_options);
145  if (empty($original_order) && $original_order !== false) {
146  $qb->addOrderBy('rv.posted', 'desc');
147  }
148 
149  if ($limit > 0) {
150  $qb->setMaxResults((int) $limit);
151  $qb->setFirstResult((int) $offset);
152  }
153 
154  $callback = $callback ?: $this->options->callback;
155  if (!isset($callback)) {
156  $callback = function ($row) {
157  return new \ElggRiverItem($row);
158  };
159  }
160 
161  $items = _elgg_services()->db->getData($qb, $callback);
162 
163  if (!empty($items)) {
164  $preload = array_filter($items, function($e) {
165  return $e instanceof \ElggRiverItem;
166  });
167 
168  _elgg_services()->entityPreloader->preload($preload, [
169  'subject_guid',
170  'object_guid',
171  'target_guid',
172  ]);
173  }
174 
175  return $items;
176  }
177 
184  public function execute() {
185  if ($this->options->annotation_calculation) {
186  $clauses = $this->options->annotation_name_value_pairs;
187  if (count($clauses) > 1 && $this->options->annotation_name_value_pairs_operator !== 'OR') {
188  throw new LogicException('Annotation calculation can not be performed on multiple annotation name value pairs merged with AND');
189  }
190 
191  $clause = array_shift($clauses);
192 
193  return $this->calculate($this->options->annotation_calculation, $clause->names, 'annotation');
194  } elseif ($this->options->count) {
195  return $this->count();
196  } elseif ($this->options->batch) {
197  return $this->batch($this->options->limit, $this->options->offset, $this->options->callback);
198  } else {
199  return $this->get($this->options->limit, $this->options->offset, $this->options->callback);
200  }
201  }
202 
210  protected function buildQuery(QueryBuilder $qb) {
211  $ands = [];
212 
213  foreach ($this->options->joins as $join) {
214  $join->prepare($qb, 'rv');
215  }
216 
217  foreach ($this->options->wheres as $where) {
218  $ands[] = $where->prepare($qb, 'rv');
219  }
220 
221  $ands[] = $this->buildRiverClause($qb);
222  $ands[] = $this->buildEntityClauses($qb);
223  $ands[] = $this->buildPairedAnnotationClause($qb, $this->options->annotation_name_value_pairs, $this->options->annotation_name_value_pairs_operator);
224  $ands[] = $this->buildPairedRelationshipClause($qb, $this->options->relationship_pairs);
225 
226  $ands = $qb->merge($ands);
227 
228  if (!empty($ands)) {
229  $qb->andWhere($ands);
230  }
231 
232  return $qb;
233  }
234 
242  protected function buildRiverClause(QueryBuilder $qb) {
243  $where = new RiverWhereClause();
244  $where->ids = $this->options->ids;
245  $where->views = $this->options->views;
246  $where->action_types = $this->options->action_types;
247  $where->subject_guids = $this->options->subject_guids;
248  $where->object_guids = $this->options->object_guids;
249  $where->target_guids = $this->options->target_guids;
250  $where->created_after = $this->options->created_after;
251  $where->created_before = $this->options->created_before;
252  $where->annotation_ids = $this->options->river_annotation_ids;
253 
254  return $where->prepare($qb, 'rv');
255  }
256 
265  public function buildEntityClauses($qb) {
266  $use_access_clause = !_elgg_services()->userCapabilities->canBypassPermissionsCheck();
267 
268  $ands = [];
269 
270  if (!empty($this->options->subject_guids) || $use_access_clause) {
271  $qb->joinEntitiesTable('rv', 'subject_guid', 'inner', 'se');
272  $subject = new EntityWhereClause();
273  $subject->guids = $this->options->subject_guids;
274  $ands[] = $subject->prepare($qb, 'se');
275  }
276 
277  if (!empty($this->options->object_guids) || $use_access_clause || !empty($this->options->type_subtype_pairs)) {
278  $qb->joinEntitiesTable('rv', 'object_guid', 'inner', 'oe');
279  $object = new EntityWhereClause();
280  $object->guids = $this->options->object_guids;
281  $object->type_subtype_pairs = $this->options->type_subtype_pairs;
282  $ands[] = $object->prepare($qb, 'oe');
283  }
284 
285  if (!empty($this->options->target_guids) || $use_access_clause) {
286  $target_ors = [];
287  $qb->joinEntitiesTable('rv', 'target_guid', 'left', 'te');
288  $target = new EntityWhereClause();
289  $target->guids = $this->options->target_guids;
290  $target_ors[] = $target->prepare($qb, 'te');
291  // Note the LEFT JOIN
292  $target_ors[] = $qb->compare('te.guid', 'IS NULL');
293  $ands[] = $qb->merge($target_ors, 'OR');
294  }
295 
296  return $qb->merge($ands);
297  }
298 
309  protected function buildPairedAnnotationClause(QueryBuilder $qb, $clauses, $boolean = 'AND') {
310  $parts = [];
311 
312  foreach ($clauses as $clause) {
313  if (strtoupper($boolean) === 'OR' || count($clauses) === 1) {
314  $joined_alias = 'n_table';
315  } else {
316  $joined_alias = $qb->getNextJoinAlias();
317  }
318 
319  $joins = $qb->getQueryPart('join');
320  $is_joined = false;
321  if (!empty($joins['rv'])) {
322  foreach ($joins['rv'] as $join) {
323  if ($join['joinAlias'] === $joined_alias) {
324  $is_joined = true;
325  }
326  }
327  }
328 
329  if (!$is_joined) {
330  $qb->join('rv', 'annotations', $joined_alias, "$joined_alias.id = rv.annotation_id");
331  }
332 
333  $parts[] = $clause->prepare($qb, $joined_alias);
334  }
335 
336  return $qb->merge($parts, $boolean);
337  }
338 
348  protected function buildPairedRelationshipClause(QueryBuilder $qb, $clauses, $boolean = 'AND') {
349  $parts = [];
350 
351  foreach ($clauses as $clause) {
352  $join_on = $clause->join_on === 'guid' ? 'subject_guid' : $clause->join_on;
353  if (strtoupper($boolean) == 'OR' || count($clauses) === 1) {
354  $joined_alias = $qb->joinRelationshipTable('rv', $join_on, null, $clause->inverse, 'inner', 'r');
355  } else {
356  $joined_alias = $qb->joinRelationshipTable('rv', $join_on, $clause->names, $clause->inverse);
357  }
358 
359  $parts[] = $clause->prepare($qb, $joined_alias);
360  }
361 
362  return $qb->merge($parts, $boolean);
363  }
364 }
if(!$comment instanceof\ElggComment||!$comment->canEdit()) $target
Definition: edit.php:15
batch($limit=null, $offset=null, $callback=null)
Fetch rows as an ElggBatch.
Definition: Repository.php:127
calculate($function, $property, $property_type= 'annotation')
Performs a mathematical calculation on river annotations.
Definition: River.php:98
if($id< 1) $annotation
Definition: delete.php:11
$defaults
Generic entity header upload helper.
Definition: header.php:6
if(empty($count)) $offset
Definition: pagination.php:26
buildEntityClauses($qb)
Add subject, object and target clauses Make sure all three are accessible by the user.
Definition: River.php:265
Exception thrown if a value does not adhere to a defined valid data domain.
Database abstraction query builder.
buildQuery(QueryBuilder $qb)
Build a database query.
Definition: River.php:210
Builds queries for matching annotations against their properties.
execute()
Execute the query resolving calculation, count and/or batch options.
Definition: River.php:184
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
$items
Definition: delete.php:8
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
buildPairedRelationshipClause(QueryBuilder $qb, $clauses, $boolean= 'AND')
Process relationship pairs.
Definition: River.php:348
River repository contains methods for fetching/counting river items.
Definition: River.php:18
Abstract methods for interfacing with the database.
Definition: Repository.php:16
$limit
Definition: pagination.php:28
Builds queries for matching river items against their properties.
Builds queries for filtering entities by their properties in the entities table.
join($fromAlias, $join, $alias, $condition=null)
{}
expandInto(QueryBuilder $qb, $table_alias=null)
Extend query builder with select, group_by, having and order_by clauses from $options.
Definition: Repository.php:254
Exception that represents error in the program logic.
buildPairedAnnotationClause(QueryBuilder $qb, $clauses, $boolean= 'AND')
Process annotation name value pairs Joins the annotation table on entity guid in the entities table a...
Definition: River.php:309
getNextJoinAlias()
Get an index of the next available join alias.
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
if($email instanceof\Elgg\Email) $object
Definition: body.php:24
merge($parts=null, $boolean= 'AND')
Merges multiple composite expressions with a boolean.
buildRiverClause(QueryBuilder $qb)
Process river properties.
Definition: River.php:242
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
joinRelationshipTable($from_alias= '', $from_column= 'guid', $name=null, $inverse=false, $join_type= 'inner', $joined_alias=null)
Join relationship table from alias and return joined table alias.
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: River.php:68
__construct(array $options=[])
{}
Definition: River.php:23
$qb
Definition: queue.php:11
$subject
Definition: useradd.php:54