1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Elgg\Database; |
4
|
|
|
|
5
|
|
|
use Closure; |
6
|
|
|
use Doctrine\DBAL\Query\Expression\CompositeExpression; |
7
|
|
|
use Elgg\Database\Clauses\AnnotationWhereClause; |
8
|
|
|
use Elgg\Database\Clauses\EntityWhereClause; |
9
|
|
|
use Elgg\Database\Clauses\MetadataWhereClause; |
10
|
|
|
use Elgg\Database\Clauses\PrivateSettingWhereClause; |
11
|
|
|
use Elgg\Database\Clauses\RelationshipWhereClause; |
12
|
|
|
use ElggAnnotation; |
13
|
|
|
use ElggEntity; |
14
|
|
|
use InvalidArgumentException; |
15
|
|
|
use InvalidParameterException; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Annotation repository contains methods for fetching annotations from database or performing |
19
|
|
|
* calculations on entity properties. |
20
|
|
|
* |
21
|
|
|
* API IN FLUX Do not access the methods directly, use elgg_get_annotations() instead |
22
|
|
|
* |
23
|
|
|
* @access private |
24
|
|
|
*/ |
25
|
|
|
class Annotations extends Repository { |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* {@inheritdoc} |
29
|
|
|
*/ |
30
|
219 |
|
public function count() { |
31
|
219 |
|
$qb = Select::fromTable('annotations', 'n_table'); |
32
|
|
|
|
33
|
219 |
|
$count_expr = $this->options->distinct ? "DISTINCT n_table.id" : "*"; |
34
|
219 |
|
$qb->select("COUNT({$count_expr}) AS total"); |
35
|
|
|
|
36
|
219 |
|
$qb = $this->buildQuery($qb); |
37
|
|
|
|
38
|
218 |
|
$result = _elgg_services()->db->getDataRow($qb); |
39
|
|
|
|
40
|
218 |
|
if (!$result) { |
|
|
|
|
41
|
3 |
|
return 0; |
42
|
|
|
} |
43
|
|
|
|
44
|
215 |
|
return (int) $result->total; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Performs a mathematical calculation on metadata or metadata entity's properties |
49
|
|
|
* |
50
|
|
|
* @param string $function Valid numeric function |
51
|
|
|
* @param string $property Property name |
52
|
|
|
* @param string $property_type 'attribute'|'metadata'|'annotation'|'private_setting' |
53
|
|
|
* |
54
|
|
|
* @return int|float |
55
|
|
|
* @throws InvalidParameterException |
56
|
|
|
*/ |
57
|
10 |
|
public function calculate($function, $property, $property_type = null) { |
58
|
|
|
|
59
|
10 |
|
if (!in_array(strtolower($function), QueryBuilder::$calculations)) { |
60
|
1 |
|
throw new InvalidArgumentException("'$function' is not a valid numeric function"); |
61
|
|
|
} |
62
|
|
|
|
63
|
9 |
|
if (!isset($property_type)) { |
64
|
1 |
|
$property_type = 'annotation'; |
65
|
|
|
} |
66
|
|
|
|
67
|
9 |
|
$qb = Select::fromTable('annotations', 'n_table'); |
68
|
|
|
|
69
|
9 |
|
switch ($property_type) { |
70
|
|
|
case 'attribute': |
71
|
2 |
|
if (!in_array($property, ElggEntity::$primary_attr_names)) { |
72
|
1 |
|
throw new InvalidParameterException("'$property' is not a valid attribute"); |
73
|
|
|
} |
74
|
|
|
|
75
|
1 |
|
$alias = $qb->joinEntitiesTable('n_table', 'entity_guid', 'inner', 'e'); |
76
|
1 |
|
$qb->select("{$function}({$alias}.{$property}) AS calculation"); |
77
|
1 |
|
break; |
78
|
|
|
|
79
|
|
|
case 'annotation' : |
80
|
5 |
|
$alias = 'n_table'; |
81
|
5 |
|
if (!empty($this->options->annotation_name_value_pairs) && $this->options->annotation_name_value_pairs[0]->names != $property) { |
82
|
1 |
|
$alias = $qb->joinAnnotationTable('n_table', 'entity_guid', $property); |
83
|
|
|
} |
84
|
5 |
|
$qb->select("{$function}($alias.value) AS calculation"); |
85
|
5 |
|
break; |
86
|
|
|
|
87
|
|
|
case 'metadata' : |
88
|
1 |
|
$alias = $qb->joinMetadataTable('n_table', 'entity_guid', $property); |
89
|
1 |
|
$qb->select("{$function}({$alias}.value) AS calculation"); |
90
|
1 |
|
break; |
91
|
|
|
|
92
|
|
|
case 'private_setting' : |
93
|
1 |
|
$alias = $qb->joinPrivateSettingsTable('n_table', 'entity_guid', $property); |
94
|
1 |
|
$qb->select("{$function}({$alias}.value) AS calculation"); |
95
|
1 |
|
break; |
96
|
|
|
} |
97
|
|
|
|
98
|
8 |
|
$qb = $this->buildQuery($qb); |
99
|
|
|
|
100
|
8 |
|
$result = _elgg_services()->db->getDataRow($qb); |
101
|
|
|
|
102
|
8 |
|
if (!$result) { |
|
|
|
|
103
|
|
|
return 0; |
104
|
|
|
} |
105
|
|
|
|
106
|
8 |
|
return (int) $result->calculation; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Fetch metadata |
111
|
|
|
* |
112
|
|
|
* @param int $limit Limit |
113
|
|
|
* @param int $offset Offset |
114
|
|
|
* @param callable $callback Custom callback |
115
|
|
|
* |
116
|
|
|
* @return ElggAnnotation[] |
117
|
|
|
*/ |
118
|
49 |
|
public function get($limit = null, $offset = null, $callback = null) { |
119
|
|
|
|
120
|
49 |
|
$qb = Select::fromTable('annotations', 'n_table'); |
121
|
|
|
|
122
|
49 |
|
$distinct = $this->options->distinct ? "DISTINCT" : ""; |
123
|
49 |
|
$qb->select("$distinct n_table.*"); |
124
|
|
|
|
125
|
49 |
|
$this->expandInto($qb, 'n_table'); |
126
|
|
|
|
127
|
49 |
|
$qb = $this->buildQuery($qb); |
128
|
|
|
|
129
|
|
|
// Keeping things backwards compatible |
130
|
48 |
|
$original_order = elgg_extract('order_by', $this->options->__original_options); |
131
|
48 |
|
if (empty($original_order) && $original_order !== false) { |
132
|
37 |
|
$qb->addOrderBy('n_table.time_created', 'asc'); |
133
|
37 |
|
$qb->addOrderBy('n_table.id', 'asc'); |
134
|
|
|
} |
135
|
|
|
|
136
|
48 |
|
if ($limit) { |
|
|
|
|
137
|
47 |
|
$qb->setMaxResults((int) $limit); |
138
|
47 |
|
$qb->setFirstResult((int) $offset); |
139
|
|
|
} |
140
|
|
|
|
141
|
48 |
|
$callback = $callback ? : $this->options->callback; |
142
|
48 |
|
if (!isset($callback)) { |
143
|
36 |
|
$callback = function ($row) { |
144
|
35 |
|
return new ElggAnnotation($row); |
145
|
36 |
|
}; |
146
|
|
|
} |
147
|
|
|
|
148
|
48 |
|
$results = _elgg_services()->db->getData($qb, $callback); |
149
|
48 |
|
if ($results && $this->options->preload_owners) { |
|
|
|
|
150
|
1 |
|
_elgg_services()->entityPreloader->preload($results, ['owner_guid']); |
151
|
|
|
} |
152
|
|
|
|
153
|
48 |
|
return $results; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Execute the query resolving calculation, count and/or batch options |
158
|
|
|
* |
159
|
|
|
* @return array|\ElggData[]|ElggAnnotation[]|false|int |
160
|
|
|
* @throws \LogicException |
161
|
|
|
*/ |
162
|
246 |
|
public function execute() { |
163
|
|
|
|
164
|
246 |
|
if ($this->options->annotation_calculation) { |
165
|
5 |
|
$clauses = $this->options->annotation_name_value_pairs; |
166
|
5 |
|
if (count($clauses) > 1 && $this->options->annotation_name_value_pairs_operator !== 'OR') { |
167
|
2 |
|
throw new \LogicException("Annotation calculation can not be performed on multiple annotation name value pairs merged with AND"); |
168
|
|
|
} |
169
|
|
|
|
170
|
3 |
|
$clause = array_shift($clauses); |
171
|
|
|
|
172
|
3 |
|
return $this->calculate($this->options->annotation_calculation, $clause->names, 'annotation'); |
173
|
243 |
|
} else if ($this->options->metadata_calculation) { |
174
|
1 |
|
$clauses = $this->options->metadata_name_value_pairs; |
175
|
1 |
|
if (count($clauses) > 1 && $this->options->metadata_name_value_pairs_operator !== 'OR') { |
176
|
|
|
throw new \LogicException("Metadata calculation can not be performed on multiple metadata name value pairs merged with AND"); |
177
|
|
|
} |
178
|
|
|
|
179
|
1 |
|
$clause = array_shift($clauses); |
180
|
|
|
|
181
|
1 |
|
return $this->calculate($this->options->metadata_calculation, $clause->names, 'metadata'); |
182
|
242 |
|
} else if ($this->options->count) { |
183
|
219 |
|
return $this->count(); |
184
|
239 |
|
} else if ($this->options->batch) { |
185
|
217 |
|
return $this->batch($this->options->limit, $this->options->offset, $this->options->callback); |
186
|
|
|
} else { |
187
|
49 |
|
return $this->get($this->options->limit, $this->options->offset, $this->options->callback); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Build a database query |
193
|
|
|
* |
194
|
|
|
* @param QueryBuilder $qb |
195
|
|
|
* |
196
|
|
|
* @return QueryBuilder |
197
|
|
|
*/ |
198
|
248 |
|
protected function buildQuery(QueryBuilder $qb) { |
199
|
|
|
|
200
|
248 |
|
$ands = []; |
201
|
|
|
|
202
|
248 |
|
foreach ($this->options->joins as $join) { |
203
|
1 |
|
$join->prepare($qb, 'n_table'); |
204
|
|
|
} |
205
|
|
|
|
206
|
248 |
|
foreach ($this->options->wheres as $where) { |
207
|
2 |
|
$ands[] = $where->prepare($qb, 'n_table'); |
208
|
|
|
} |
209
|
|
|
|
210
|
248 |
|
$ands[] = $this->buildPairedAnnotationClause($qb, $this->options->annotation_name_value_pairs, $this->options->annotation_name_value_pairs_operator); |
211
|
248 |
|
$ands[] = $this->buildEntityWhereClause($qb); |
212
|
246 |
|
$ands[] = $this->buildPairedMetadataClause($qb, $this->options->metadata_name_value_pairs, $this->options->metadata_name_value_pairs_operator); |
213
|
246 |
|
$ands[] = $this->buildPairedMetadataClause($qb, $this->options->search_name_value_pairs, 'OR'); |
214
|
246 |
|
$ands[] = $this->buildPairedPrivateSettingsClause($qb, $this->options->private_setting_name_value_pairs, $this->options->private_setting_name_value_pairs_operator); |
215
|
246 |
|
$ands[] = $this->buildPairedRelationshipClause($qb, $this->options->relationship_pairs); |
216
|
|
|
|
217
|
246 |
|
$ands = $qb->merge($ands); |
218
|
|
|
|
219
|
246 |
|
if (!empty($ands)) { |
220
|
246 |
|
$qb->andWhere($ands); |
221
|
|
|
} |
222
|
|
|
|
223
|
246 |
|
return $qb; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Process entity attribute wheres |
228
|
|
|
* Joins entities table on entity guid in annotations table and applies where clauses |
229
|
|
|
* |
230
|
|
|
* @param QueryBuilder $qb Query builder |
231
|
|
|
* |
232
|
|
|
* @return Closure|CompositeExpression|mixed|null|string |
233
|
|
|
*/ |
234
|
248 |
|
protected function buildEntityWhereClause(QueryBuilder $qb) { |
235
|
248 |
|
$joined_alias = $qb->joinEntitiesTable('n_table', 'entity_guid', 'inner', 'e'); |
236
|
248 |
|
return EntityWhereClause::factory($this->options)->prepare($qb, $joined_alias); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Process annotation name value pairs |
241
|
|
|
* Applies where clauses to the selected annotation table |
242
|
|
|
* |
243
|
|
|
* @param QueryBuilder $qb Query builder |
244
|
|
|
* @param AnnotationWhereClause[] $clauses Where clauses |
245
|
|
|
* @param string $boolean Merge boolean |
246
|
|
|
* |
247
|
|
|
* @return CompositeExpression|string |
248
|
|
|
*/ |
249
|
248 |
|
protected function buildPairedAnnotationClause(QueryBuilder $qb, $clauses, $boolean = 'AND') { |
250
|
248 |
|
$parts = []; |
251
|
|
|
|
252
|
248 |
|
if (empty($clauses)) { |
253
|
|
|
// We need to make sure that enabled and access clauses are appended to the query |
254
|
232 |
|
$clauses[] = new AnnotationWhereClause(); |
255
|
|
|
} |
256
|
|
|
|
257
|
248 |
|
foreach ($clauses as $clause) { |
258
|
248 |
|
$parts[] = $clause->prepare($qb, 'n_table'); |
259
|
|
|
} |
260
|
|
|
|
261
|
248 |
|
return $qb->merge($parts, $boolean); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Process metadata name value pairs |
266
|
|
|
* Joins metadata table on entity_guid in the annotations table and applies where clauses |
267
|
|
|
* |
268
|
|
|
* @param QueryBuilder $qb Query builder |
269
|
|
|
* @param MetadataWhereClause[] $clauses Where clauses |
270
|
|
|
* @param string $boolean Merge boolean |
271
|
|
|
* |
272
|
|
|
* @return CompositeExpression|string |
273
|
|
|
*/ |
274
|
246 |
|
protected function buildPairedMetadataClause(QueryBuilder $qb, $clauses, $boolean = 'AND') { |
275
|
246 |
|
$parts = []; |
276
|
|
|
|
277
|
246 |
|
foreach ($clauses as $clause) { |
278
|
3 |
|
if (strtoupper($boolean) === 'OR' || count($clauses) > 1) { |
279
|
2 |
|
$joined_alias = $qb->joinMetadataTable('n_table', 'entity_guid'); |
280
|
|
|
} else { |
281
|
1 |
|
$joined_alias = $qb->joinMetadataTable('n_table', 'entity_guid', $clause->names); |
282
|
|
|
} |
283
|
3 |
|
$parts[] = $clause->prepare($qb, $joined_alias); |
284
|
|
|
} |
285
|
|
|
|
286
|
246 |
|
return $qb->merge($parts, $boolean); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Process private settings name value pairs |
291
|
|
|
* Joins private settings table on entity_guid in the annotations table and applies where clauses |
292
|
|
|
* |
293
|
|
|
* @param QueryBuilder $qb Query builder |
294
|
|
|
* @param PrivateSettingWhereClause[] $clauses Where clauses |
295
|
|
|
* @param string $boolean Merge boolean |
296
|
|
|
* |
297
|
|
|
* @return CompositeExpression|string |
298
|
|
|
*/ |
299
|
246 |
|
protected function buildPairedPrivateSettingsClause(QueryBuilder $qb, $clauses, $boolean = 'AND') { |
300
|
246 |
|
$parts = []; |
301
|
|
|
|
302
|
246 |
|
foreach ($clauses as $clause) { |
303
|
3 |
|
if (strtoupper($boolean) === 'OR' || count($clauses) > 1) { |
304
|
2 |
|
$joined_alias = $qb->joinPrivateSettingsTable('n_table', 'entity_guid'); |
305
|
|
|
} else { |
306
|
1 |
|
$joined_alias = $qb->joinPrivateSettingsTable('n_table', 'entity_guid', $clause->names); |
307
|
|
|
} |
308
|
3 |
|
$parts[] = $clause->prepare($qb, $joined_alias); |
309
|
|
|
} |
310
|
|
|
|
311
|
246 |
|
return $qb->merge($parts, $boolean); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Process relationship name value pairs |
316
|
|
|
* Joins relationship table on entity_guid in the annotations table and applies where clauses |
317
|
|
|
* |
318
|
|
|
* @param QueryBuilder $qb Query builder |
319
|
|
|
* @param RelationshipWhereClause[] $clauses Where clauses |
320
|
|
|
* @param string $boolean Merge boolean |
321
|
|
|
* |
322
|
|
|
* @return CompositeExpression|string |
323
|
|
|
*/ |
324
|
246 |
|
protected function buildPairedRelationshipClause(QueryBuilder $qb, $clauses, $boolean = 'AND') { |
325
|
246 |
|
$parts = []; |
326
|
|
|
|
327
|
246 |
|
foreach ($clauses as $clause) { |
328
|
2 |
|
$join_on = $clause->join_on == 'guid' ? 'entity_guid' : $clause->join_on; |
329
|
2 |
|
if (strtoupper($boolean) == 'OR' || count($clauses) > 1) { |
330
|
1 |
|
$joined_alias = $qb->joinRelationshipTable('n_table', $join_on, null, $clause->inverse); |
331
|
|
|
} else { |
332
|
1 |
|
$joined_alias = $qb->joinRelationshipTable('n_table', $join_on, $clause->names, $clause->inverse); |
|
|
|
|
333
|
|
|
} |
334
|
2 |
|
$parts[] = $clause->prepare($qb, $joined_alias); |
335
|
|
|
} |
336
|
|
|
|
337
|
246 |
|
return $qb->merge($parts, $boolean); |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.