1
|
|
|
<?php |
2
|
|
|
namespace Elgg\Database; |
3
|
|
|
|
4
|
|
|
use IncompleteEntityException; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
8
|
|
|
* |
9
|
|
|
* @access private |
10
|
|
|
* |
11
|
|
|
* @package Elgg.Core |
12
|
|
|
* @subpackage Database |
13
|
|
|
* @since 1.10.0 |
14
|
|
|
*/ |
15
|
|
|
class EntityTable { |
16
|
|
|
/** |
17
|
|
|
* Global Elgg configuration |
18
|
|
|
* |
19
|
|
|
* @var \stdClass |
20
|
|
|
*/ |
21
|
|
|
private $CONFIG; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Constructor |
25
|
|
|
*/ |
26
|
2 |
|
public function __construct() { |
27
|
2 |
|
global $CONFIG; |
28
|
2 |
|
$this->CONFIG = $CONFIG; |
29
|
2 |
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Returns a database row from the entities table. |
33
|
|
|
* |
34
|
|
|
* @tip Use get_entity() to return the fully loaded entity. |
35
|
|
|
* |
36
|
|
|
* @warning This will only return results if a) it exists, b) you have access to it. |
37
|
|
|
* see {@link _elgg_get_access_where_sql()}. |
38
|
|
|
* |
39
|
|
|
* @param int $guid The GUID of the object to extract |
40
|
|
|
* |
41
|
|
|
* @return \stdClass|false |
42
|
|
|
* @see entity_row_to_elggstar() |
43
|
|
|
* @access private |
44
|
|
|
*/ |
45
|
|
|
function getRow($guid) { |
46
|
|
|
|
47
|
|
|
|
48
|
|
|
if (!$guid) { |
49
|
|
|
return false; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
$guid = (int) $guid; |
53
|
|
|
$access = _elgg_get_access_where_sql(array('table_alias' => '')); |
54
|
|
|
|
55
|
|
|
return _elgg_services()->db->getDataRow("SELECT * from {$this->CONFIG->dbprefix}entities where guid=$guid and $access"); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Create an Elgg* object from a given entity row. |
60
|
|
|
* |
61
|
|
|
* Handles loading all tables into the correct class. |
62
|
|
|
* |
63
|
|
|
* @param \stdClass $row The row of the entry in the entities table. |
64
|
|
|
* |
65
|
|
|
* @return \ElggEntity|false |
66
|
|
|
* @see get_entity_as_row() |
67
|
|
|
* @see add_subtype() |
68
|
|
|
* @see get_entity() |
69
|
|
|
* @access private |
70
|
|
|
* |
71
|
|
|
* @throws \ClassException|\InstallationException |
72
|
|
|
*/ |
73
|
|
|
function rowToElggStar($row) { |
74
|
|
|
if (!($row instanceof \stdClass)) { |
75
|
|
|
return $row; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
if ((!isset($row->guid)) || (!isset($row->subtype))) { |
79
|
|
|
return $row; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$new_entity = false; |
83
|
|
|
|
84
|
|
|
// Create a memcache cache if we can |
85
|
|
|
static $newentity_cache; |
86
|
|
|
if ((!$newentity_cache) && (is_memcache_available())) { |
87
|
|
|
$newentity_cache = new \ElggMemcache('new_entity_cache'); |
88
|
|
|
} |
89
|
|
|
if ($newentity_cache) { |
90
|
|
|
$new_entity = $newentity_cache->load($row->guid); |
91
|
|
|
} |
92
|
|
|
if ($new_entity) { |
93
|
|
|
return $new_entity; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
// load class for entity if one is registered |
97
|
|
|
$classname = get_subtype_class_from_id($row->subtype); |
98
|
|
View Code Duplication |
if ($classname != "") { |
99
|
|
|
if (class_exists($classname)) { |
100
|
|
|
$new_entity = new $classname($row); |
101
|
|
|
|
102
|
|
|
if (!($new_entity instanceof \ElggEntity)) { |
103
|
|
|
$msg = $classname . " is not a " . '\ElggEntity' . "."; |
104
|
|
|
throw new \ClassException($msg); |
105
|
|
|
} |
106
|
|
|
} else { |
107
|
|
|
error_log("Class '" . $classname . "' was not found, missing plugin?"); |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
if (!$new_entity) { |
112
|
|
|
//@todo Make this into a function |
113
|
|
|
switch ($row->type) { |
114
|
|
|
case 'object' : |
115
|
|
|
$new_entity = new \ElggObject($row); |
116
|
|
|
break; |
117
|
|
|
case 'user' : |
118
|
|
|
$new_entity = new \ElggUser($row); |
119
|
|
|
break; |
120
|
|
|
case 'group' : |
121
|
|
|
$new_entity = new \ElggGroup($row); |
122
|
|
|
break; |
123
|
|
|
case 'site' : |
124
|
|
|
$new_entity = new \ElggSite($row); |
125
|
|
|
break; |
126
|
|
|
default: |
127
|
|
|
$msg = "Entity type " . $row->type . " is not supported."; |
128
|
|
|
throw new \InstallationException($msg); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// Cache entity if we have a cache available |
133
|
|
|
if (($newentity_cache) && ($new_entity)) { |
134
|
|
|
$newentity_cache->save($new_entity->guid, $new_entity); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
return $new_entity; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Loads and returns an entity object from a guid. |
142
|
|
|
* |
143
|
|
|
* @param int $guid The GUID of the entity |
144
|
|
|
* @param string $type The type of the entity. If given, even an existing entity with the given GUID |
145
|
|
|
* will not be returned unless its type matches. |
146
|
|
|
* |
147
|
|
|
* @return \ElggEntity The correct Elgg or custom object based upon entity type and subtype |
148
|
|
|
*/ |
149
|
1 |
|
function get($guid, $type = '') { |
150
|
|
|
// We could also use: if (!(int) $guid) { return false }, |
151
|
|
|
// but that evaluates to a false positive for $guid = true. |
152
|
|
|
// This is a bit slower, but more thorough. |
153
|
1 |
|
if (!is_numeric($guid) || $guid === 0 || $guid === '0') { |
154
|
1 |
|
return false; |
|
|
|
|
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// Check local cache first |
158
|
|
|
$new_entity = _elgg_retrieve_cached_entity($guid); |
|
|
|
|
159
|
|
|
if ($new_entity) { |
160
|
|
|
if ($type) { |
161
|
|
|
return elgg_instanceof($new_entity, $type) ? $new_entity : false; |
|
|
|
|
162
|
|
|
} |
163
|
|
|
return $new_entity; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$options = [ |
167
|
|
|
'guid' => $guid, |
168
|
|
|
'limit' => 1, |
169
|
|
|
'site_guids' => ELGG_ENTITIES_ANY_VALUE, // for BC with get_entity, allow matching any site |
170
|
|
|
]; |
171
|
|
|
if ($type) { |
172
|
|
|
$options['type'] = $type; |
173
|
|
|
} |
174
|
|
|
$entities = $this->getEntities($options); |
175
|
|
|
return $entities ? $entities[0] : false; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Does an entity exist? |
180
|
|
|
* |
181
|
|
|
* This function checks for the existence of an entity independent of access |
182
|
|
|
* permissions. It is useful for situations when a user cannot access an entity |
183
|
|
|
* and it must be determined whether entity has been deleted or the access level |
184
|
|
|
* has changed. |
185
|
|
|
* |
186
|
|
|
* @param int $guid The GUID of the entity |
187
|
|
|
* |
188
|
|
|
* @return bool |
189
|
|
|
*/ |
190
|
|
|
function exists($guid) { |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
$guid = sanitize_int($guid); |
194
|
|
|
|
195
|
|
|
$query = "SELECT count(*) as total FROM {$this->CONFIG->dbprefix}entities WHERE guid = $guid"; |
196
|
|
|
$result = _elgg_services()->db->getDataRow($query); |
197
|
|
|
if ($result->total == 0) { |
198
|
|
|
return false; |
199
|
|
|
} else { |
200
|
|
|
return true; |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Enable an entity. |
206
|
|
|
* |
207
|
|
|
* @param int $guid GUID of entity to enable |
208
|
|
|
* @param bool $recursive Recursively enable all entities disabled with the entity? |
209
|
|
|
* |
210
|
|
|
* @return bool |
211
|
|
|
*/ |
212
|
|
|
function enable($guid, $recursive = true) { |
213
|
|
|
|
214
|
|
|
// Override access only visible entities |
215
|
|
|
$old_access_status = access_get_show_hidden_status(); |
216
|
|
|
access_show_hidden_entities(true); |
217
|
|
|
|
218
|
|
|
$result = false; |
219
|
|
|
$entity = get_entity($guid); |
220
|
|
|
if ($entity) { |
221
|
|
|
$result = $entity->enable($recursive); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
access_show_hidden_entities($old_access_status); |
225
|
|
|
return $result; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Returns an array of entities with optional filtering. |
230
|
|
|
* |
231
|
|
|
* Entities are the basic unit of storage in Elgg. This function |
232
|
|
|
* provides the simplest way to get an array of entities. There |
233
|
|
|
* are many options available that can be passed to filter |
234
|
|
|
* what sorts of entities are returned. |
235
|
|
|
* |
236
|
|
|
* @tip To output formatted strings of entities, use {@link elgg_list_entities()} and |
237
|
|
|
* its cousins. |
238
|
|
|
* |
239
|
|
|
* @tip Plural arguments can be written as singular if only specifying a |
240
|
|
|
* single element. ('type' => 'object' vs 'types' => array('object')). |
241
|
|
|
* |
242
|
|
|
* @param array $options Array in format: |
243
|
|
|
* |
244
|
|
|
* types => null|STR entity type (type IN ('type1', 'type2') |
245
|
|
|
* Joined with subtypes by AND. See below) |
246
|
|
|
* |
247
|
|
|
* subtypes => null|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2)) |
248
|
|
|
* Use ELGG_ENTITIES_NO_VALUE to match the default subtype. |
249
|
|
|
* Use ELGG_ENTITIES_ANY_VALUE to match any subtype. |
250
|
|
|
* |
251
|
|
|
* type_subtype_pairs => null|ARR (array('type' => 'subtype')) |
252
|
|
|
* array( |
253
|
|
|
* 'object' => array('blog', 'file'), // All objects with subtype of 'blog' or 'file' |
254
|
|
|
* 'user' => ELGG_ENTITY_ANY_VALUE, // All users irrespective of subtype |
255
|
|
|
* ); |
256
|
|
|
* |
257
|
|
|
* guids => null|ARR Array of entity guids |
258
|
|
|
* |
259
|
|
|
* owner_guids => null|ARR Array of owner guids |
260
|
|
|
* |
261
|
|
|
* container_guids => null|ARR Array of container_guids |
262
|
|
|
* |
263
|
|
|
* site_guids => null (current_site)|ARR Array of site_guid |
264
|
|
|
* |
265
|
|
|
* order_by => null (time_created desc)|STR SQL order by clause |
266
|
|
|
* |
267
|
|
|
* reverse_order_by => BOOL Reverse the default order by clause |
268
|
|
|
* |
269
|
|
|
* limit => null (10)|INT SQL limit clause (0 means no limit) |
270
|
|
|
* |
271
|
|
|
* offset => null (0)|INT SQL offset clause |
272
|
|
|
* |
273
|
|
|
* created_time_lower => null|INT Created time lower boundary in epoch time |
274
|
|
|
* |
275
|
|
|
* created_time_upper => null|INT Created time upper boundary in epoch time |
276
|
|
|
* |
277
|
|
|
* modified_time_lower => null|INT Modified time lower boundary in epoch time |
278
|
|
|
* |
279
|
|
|
* modified_time_upper => null|INT Modified time upper boundary in epoch time |
280
|
|
|
* |
281
|
|
|
* count => true|false return a count instead of entities |
282
|
|
|
* |
283
|
|
|
* wheres => array() Additional where clauses to AND together |
284
|
|
|
* |
285
|
|
|
* joins => array() Additional joins |
286
|
|
|
* |
287
|
|
|
* preload_owners => bool (false) If set to true, this function will preload |
288
|
|
|
* all the owners of the returned entities resulting in better |
289
|
|
|
* performance if those owners need to be displayed |
290
|
|
|
* |
291
|
|
|
* preload_containers => bool (false) If set to true, this function will preload |
292
|
|
|
* all the containers of the returned entities resulting in better |
293
|
|
|
* performance if those containers need to be displayed |
294
|
|
|
* |
295
|
|
|
* |
296
|
|
|
* callback => string A callback function to pass each row through |
297
|
|
|
* |
298
|
|
|
* distinct => bool (true) If set to false, Elgg will drop the DISTINCT clause from |
299
|
|
|
* the MySQL query, which will improve performance in some situations. |
300
|
|
|
* Avoid setting this option without a full understanding of the underlying |
301
|
|
|
* SQL query Elgg creates. |
302
|
|
|
* |
303
|
|
|
* @return mixed If count, int. If not count, array. false on errors. |
304
|
|
|
* @see elgg_get_entities_from_metadata() |
305
|
|
|
* @see elgg_get_entities_from_relationship() |
306
|
|
|
* @see elgg_get_entities_from_access_id() |
307
|
|
|
* @see elgg_get_entities_from_annotations() |
308
|
|
|
* @see elgg_list_entities() |
309
|
|
|
*/ |
310
|
|
|
function getEntities(array $options = array()) { |
311
|
|
|
|
312
|
|
|
|
313
|
|
|
$defaults = array( |
314
|
|
|
'types' => ELGG_ENTITIES_ANY_VALUE, |
315
|
|
|
'subtypes' => ELGG_ENTITIES_ANY_VALUE, |
316
|
|
|
'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, |
317
|
|
|
|
318
|
|
|
'guids' => ELGG_ENTITIES_ANY_VALUE, |
319
|
|
|
'owner_guids' => ELGG_ENTITIES_ANY_VALUE, |
320
|
|
|
'container_guids' => ELGG_ENTITIES_ANY_VALUE, |
321
|
|
|
'site_guids' => $this->CONFIG->site_guid, |
322
|
|
|
|
323
|
|
|
'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE, |
324
|
|
|
'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE, |
325
|
|
|
'created_time_lower' => ELGG_ENTITIES_ANY_VALUE, |
326
|
|
|
'created_time_upper' => ELGG_ENTITIES_ANY_VALUE, |
327
|
|
|
|
328
|
|
|
'reverse_order_by' => false, |
329
|
|
|
'order_by' => 'e.time_created desc', |
330
|
|
|
'group_by' => ELGG_ENTITIES_ANY_VALUE, |
331
|
|
|
'limit' => _elgg_services()->config->get('default_limit'), |
332
|
|
|
'offset' => 0, |
333
|
|
|
'count' => false, |
334
|
|
|
'selects' => array(), |
335
|
|
|
'wheres' => array(), |
336
|
|
|
'joins' => array(), |
337
|
|
|
|
338
|
|
|
'preload_owners' => false, |
339
|
|
|
'preload_containers' => false, |
340
|
|
|
'callback' => 'entity_row_to_elggstar', |
341
|
|
|
'distinct' => true, |
342
|
|
|
|
343
|
|
|
// private API |
344
|
|
|
'__ElggBatch' => null, |
345
|
|
|
); |
346
|
|
|
|
347
|
|
|
$options = array_merge($defaults, $options); |
348
|
|
|
|
349
|
|
|
// can't use helper function with type_subtype_pair because |
350
|
|
|
// it's already an array...just need to merge it |
351
|
|
View Code Duplication |
if (isset($options['type_subtype_pair'])) { |
352
|
|
|
if (isset($options['type_subtype_pairs'])) { |
353
|
|
|
$options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'], |
354
|
|
|
$options['type_subtype_pair']); |
355
|
|
|
} else { |
356
|
|
|
$options['type_subtype_pairs'] = $options['type_subtype_pair']; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
$singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid'); |
361
|
|
|
$options = _elgg_normalize_plural_options_array($options, $singulars); |
362
|
|
|
|
363
|
|
|
$options = $this->autoJoinTables($options); |
364
|
|
|
|
365
|
|
|
// evaluate where clauses |
366
|
|
|
if (!is_array($options['wheres'])) { |
367
|
|
|
$options['wheres'] = array($options['wheres']); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
$wheres = $options['wheres']; |
371
|
|
|
|
372
|
|
|
$wheres[] = _elgg_get_entity_type_subtype_where_sql('e', $options['types'], |
373
|
|
|
$options['subtypes'], $options['type_subtype_pairs']); |
374
|
|
|
|
375
|
|
|
$wheres[] = _elgg_get_guid_based_where_sql('e.guid', $options['guids']); |
376
|
|
|
$wheres[] = _elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']); |
377
|
|
|
$wheres[] = _elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']); |
378
|
|
|
$wheres[] = _elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']); |
379
|
|
|
|
380
|
|
|
$wheres[] = _elgg_get_entity_time_where_sql('e', $options['created_time_upper'], |
381
|
|
|
$options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); |
382
|
|
|
|
383
|
|
|
// see if any functions failed |
384
|
|
|
// remove empty strings on successful functions |
385
|
|
View Code Duplication |
foreach ($wheres as $i => $where) { |
386
|
|
|
if ($where === false) { |
387
|
|
|
return false; |
388
|
|
|
} elseif (empty($where)) { |
389
|
|
|
unset($wheres[$i]); |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
// remove identical where clauses |
394
|
|
|
$wheres = array_unique($wheres); |
395
|
|
|
|
396
|
|
|
// evaluate join clauses |
397
|
|
|
if (!is_array($options['joins'])) { |
398
|
|
|
$options['joins'] = array($options['joins']); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
// remove identical join clauses |
402
|
|
|
$joins = array_unique($options['joins']); |
403
|
|
|
|
404
|
|
View Code Duplication |
foreach ($joins as $i => $join) { |
405
|
|
|
if ($join === false) { |
406
|
|
|
return false; |
407
|
|
|
} elseif (empty($join)) { |
408
|
|
|
unset($joins[$i]); |
409
|
|
|
} |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
// evalutate selects |
413
|
|
View Code Duplication |
if ($options['selects']) { |
414
|
|
|
$selects = ''; |
415
|
|
|
foreach ($options['selects'] as $select) { |
416
|
|
|
$selects .= ", $select"; |
417
|
|
|
} |
418
|
|
|
} else { |
419
|
|
|
$selects = ''; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
View Code Duplication |
if (!$options['count']) { |
423
|
|
|
$distinct = $options['distinct'] ? "DISTINCT" : ""; |
424
|
|
|
$query = "SELECT $distinct e.*{$selects} FROM {$this->CONFIG->dbprefix}entities e "; |
425
|
|
|
} else { |
426
|
|
|
// note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than GUIDs |
427
|
|
|
$count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*"; |
428
|
|
|
$query = "SELECT COUNT($count_expr) as total FROM {$this->CONFIG->dbprefix}entities e "; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
// add joins |
432
|
|
|
foreach ($joins as $j) { |
433
|
|
|
$query .= " $j "; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
// add wheres |
437
|
|
|
$query .= ' WHERE '; |
438
|
|
|
|
439
|
|
|
foreach ($wheres as $w) { |
440
|
|
|
$query .= " $w AND "; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
// Add access controls |
444
|
|
|
$query .= _elgg_get_access_where_sql(); |
445
|
|
|
|
446
|
|
|
// reverse order by |
447
|
|
|
if ($options['reverse_order_by']) { |
448
|
|
|
$options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']); |
|
|
|
|
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
if ($options['count']) { |
452
|
|
|
$total = _elgg_services()->db->getDataRow($query); |
453
|
|
|
return (int)$total->total; |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
if ($options['group_by']) { |
457
|
|
|
$query .= " GROUP BY {$options['group_by']}"; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
if ($options['order_by']) { |
461
|
|
|
$query .= " ORDER BY {$options['order_by']}"; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
View Code Duplication |
if ($options['limit']) { |
465
|
|
|
$limit = sanitise_int($options['limit'], false); |
|
|
|
|
466
|
|
|
$offset = sanitise_int($options['offset'], false); |
|
|
|
|
467
|
|
|
$query .= " LIMIT $offset, $limit"; |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
if ($options['callback'] === 'entity_row_to_elggstar') { |
471
|
|
|
if ( isset($options['wetcustom:messages']) ) |
472
|
|
|
$results = _elgg_wet_fetch_entities_from_sql($query, $options['__ElggBatch']); // Patched to allow for much faster message inbox loading with all messages being loaded into a data table |
473
|
|
|
else |
474
|
|
|
$results = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']); |
475
|
|
|
} else { |
476
|
|
|
$results = _elgg_services()->db->getData($query, $options['callback']); |
|
|
|
|
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
if (!$results) { |
|
|
|
|
480
|
|
|
// no results, no preloading |
481
|
|
|
return $results; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
// populate entity and metadata caches, and prepare $entities for preloader |
485
|
|
|
$guids = array(); |
486
|
|
View Code Duplication |
foreach ($results as $item) { |
487
|
|
|
// A custom callback could result in items that aren't \ElggEntity's, so check for them |
488
|
|
|
if ($item instanceof \ElggEntity) { |
489
|
|
|
_elgg_cache_entity($item); |
490
|
|
|
// plugins usually have only settings |
491
|
|
|
if (!$item instanceof \ElggPlugin) { |
492
|
|
|
$guids[] = $item->guid; |
493
|
|
|
} |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
// @todo Without this, recursive delete fails. See #4568 |
497
|
|
|
reset($results); |
498
|
|
|
|
499
|
|
|
if ($guids) { |
|
|
|
|
500
|
|
|
// there were entities in the result set, preload metadata for them |
501
|
|
|
_elgg_services()->metadataCache->populateFromEntities($guids); |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
if (count($results) > 1) { |
505
|
|
|
$props_to_preload = []; |
506
|
|
|
if ($options['preload_owners']) { |
507
|
|
|
$props_to_preload[] = 'owner_guid'; |
508
|
|
|
} |
509
|
|
|
if ($options['preload_containers']) { |
510
|
|
|
$props_to_preload[] = 'container_guid'; |
511
|
|
|
} |
512
|
|
|
if ($props_to_preload) { |
|
|
|
|
513
|
|
|
// note, ElggEntityPreloaderIntegrationTest assumes it can swap out |
514
|
|
|
// the preloader after boot. If you inject this component at construction |
515
|
|
|
// time that unit test will break. :/ |
516
|
|
|
_elgg_services()->entityPreloader->preload($results, $props_to_preload); |
517
|
|
|
} |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
return $results; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* Decorate getEntities() options in order to auto-join secondary tables where it's |
525
|
|
|
* safe to do so. |
526
|
|
|
* |
527
|
|
|
* @param array $options Options array in getEntities() after normalization |
528
|
|
|
* @return array |
529
|
|
|
*/ |
530
|
|
|
protected function autoJoinTables(array $options) { |
531
|
|
|
// we must be careful that the query doesn't specify any options that may join |
532
|
|
|
// tables or change the selected columns |
533
|
|
|
if (!is_array($options['types']) |
534
|
|
|
|| count($options['types']) !== 1 |
535
|
|
|
|| !empty($options['selects']) |
536
|
|
|
|| !empty($options['wheres']) |
537
|
|
|
|| !empty($options['joins']) |
538
|
|
|
|| $options['callback'] !== 'entity_row_to_elggstar' |
539
|
|
|
|| $options['count']) { |
540
|
|
|
// Too dangerous to auto-join |
541
|
|
|
return $options; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
$join_types = [ |
545
|
|
|
// Each class must have a static getExternalAttributes() : array |
546
|
|
|
'object' => 'ElggObject', |
547
|
|
|
'user' => 'ElggUser', |
548
|
|
|
'group' => 'ElggGroup', |
549
|
|
|
'site' => 'ElggSite', |
550
|
|
|
]; |
551
|
|
|
|
552
|
|
|
// We use reset() because $options['types'] may not have a numeric key |
553
|
|
|
$type = reset($options['types']); |
554
|
|
|
if (empty($join_types[$type])) { |
555
|
|
|
return $options; |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
// Get the columns we'll need to select. We can't use st.* because the order_by |
559
|
|
|
// clause may reference "guid", which MySQL will complain about being ambiguous |
560
|
|
|
if (!is_callable([$join_types[$type], 'getExternalAttributes'])) { |
561
|
|
|
// for some reason can't get external attributes. |
562
|
|
|
return $options; |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
$attributes = $join_types[$type]::getExternalAttributes(); |
|
|
|
|
566
|
|
|
foreach (array_keys($attributes) as $col) { |
567
|
|
|
$options['selects'][] = "st.$col"; |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
// join the secondary table |
571
|
|
|
$options['joins'][] = "JOIN {$this->CONFIG->dbprefix}{$type}s_entity st ON (e.guid = st.guid)"; |
572
|
|
|
|
573
|
|
|
return $options; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
/** |
577
|
|
|
* Return entities from an SQL query generated by elgg_get_entities. |
578
|
|
|
* |
579
|
|
|
* @param string $sql |
580
|
|
|
* @param \ElggBatch $batch |
581
|
|
|
* @return \ElggEntity[] |
582
|
|
|
* |
583
|
|
|
* @access private |
584
|
|
|
* @throws \LogicException |
585
|
|
|
*/ |
586
|
|
|
function fetchFromSql($sql, \ElggBatch $batch = null) { |
587
|
|
|
static $plugin_subtype; |
588
|
|
|
if (null === $plugin_subtype) { |
589
|
|
|
$plugin_subtype = get_subtype_id('object', 'plugin'); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
// Keys are types, values are columns that, if present, suggest that the secondary |
593
|
|
|
// table is already JOINed. Note it's OK if guess incorrectly because entity load() |
594
|
|
|
// will fetch any missing attributes. |
595
|
|
|
$types_to_optimize = array( |
596
|
|
|
'object' => 'title', |
597
|
|
|
'user' => 'password', |
598
|
|
|
'group' => 'name', |
599
|
|
|
'site' => 'url', |
600
|
|
|
); |
601
|
|
|
|
602
|
|
|
$rows = _elgg_services()->db->getData($sql); |
603
|
|
|
|
604
|
|
|
// guids to look up in each type |
605
|
|
|
$lookup_types = array(); |
606
|
|
|
// maps GUIDs to the $rows key |
607
|
|
|
$guid_to_key = array(); |
608
|
|
|
|
609
|
|
View Code Duplication |
if (isset($rows[0]->type, $rows[0]->subtype) |
610
|
|
|
&& $rows[0]->type === 'object' |
611
|
|
|
&& $rows[0]->subtype == $plugin_subtype) { |
612
|
|
|
// Likely the entire resultset is plugins, which have already been optimized |
613
|
|
|
// to JOIN the secondary table. In this case we allow retrieving from cache, |
614
|
|
|
// but abandon the extra queries. |
615
|
|
|
$types_to_optimize = array(); |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
// First pass: use cache where possible, gather GUIDs that we're optimizing |
619
|
|
View Code Duplication |
foreach ($rows as $i => $row) { |
620
|
|
|
if (empty($row->guid) || empty($row->type)) { |
621
|
|
|
throw new \LogicException('Entity row missing guid or type'); |
622
|
|
|
} |
623
|
|
|
$entity = _elgg_retrieve_cached_entity($row->guid); |
624
|
|
|
if ($entity) { |
625
|
|
|
$entity->refresh($row); |
626
|
|
|
$rows[$i] = $entity; |
627
|
|
|
continue; |
628
|
|
|
} |
629
|
|
|
if (isset($types_to_optimize[$row->type])) { |
630
|
|
|
// check if row already looks JOINed. |
631
|
|
|
if (isset($row->{$types_to_optimize[$row->type]})) { |
632
|
|
|
// Row probably already contains JOINed secondary table. Don't make another query just |
633
|
|
|
// to pull data that's already there |
634
|
|
|
continue; |
635
|
|
|
} |
636
|
|
|
$lookup_types[$row->type][] = $row->guid; |
637
|
|
|
$guid_to_key[$row->guid] = $i; |
638
|
|
|
} |
639
|
|
|
} |
640
|
|
|
// Do secondary queries and merge rows |
641
|
|
|
if ($lookup_types) { |
|
|
|
|
642
|
|
|
$dbprefix = _elgg_services()->config->get('dbprefix'); |
643
|
|
|
|
644
|
|
|
foreach ($lookup_types as $type => $guids) { |
645
|
|
|
$set = "(" . implode(',', $guids) . ")"; |
646
|
|
|
$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; |
647
|
|
|
$secondary_rows = _elgg_services()->db->getData($sql); |
648
|
|
View Code Duplication |
if ($secondary_rows) { |
|
|
|
|
649
|
|
|
foreach ($secondary_rows as $secondary_row) { |
650
|
|
|
$key = $guid_to_key[$secondary_row->guid]; |
651
|
|
|
// cast to arrays to merge then cast back |
652
|
|
|
$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); |
653
|
|
|
} |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
} |
657
|
|
|
// Second pass to finish conversion |
658
|
|
View Code Duplication |
foreach ($rows as $i => $row) { |
659
|
|
|
if ($row instanceof \ElggEntity) { |
660
|
|
|
continue; |
661
|
|
|
} else { |
662
|
|
|
try { |
663
|
|
|
$rows[$i] = entity_row_to_elggstar($row); |
664
|
|
|
} catch (IncompleteEntityException $e) { |
665
|
|
|
// don't let incomplete entities throw fatal errors |
666
|
|
|
unset($rows[$i]); |
667
|
|
|
|
668
|
|
|
// report incompletes to the batch process that spawned this query |
669
|
|
|
if ($batch) { |
670
|
|
|
$batch->reportIncompleteEntity($row); |
671
|
|
|
} |
672
|
|
|
} |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
return $rows; |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
/** |
679
|
|
|
* Returns SQL where clause for type and subtype on main entity table |
680
|
|
|
* |
681
|
|
|
* @param string $table Entity table prefix as defined in SELECT...FROM entities $table |
682
|
|
|
* @param null|array $types Array of types or null if none. |
683
|
|
|
* @param null|array $subtypes Array of subtypes or null if none |
684
|
|
|
* @param null|array $pairs Array of pairs of types and subtypes |
685
|
|
|
* |
686
|
|
|
* @return false|string |
687
|
|
|
* @access private |
688
|
|
|
*/ |
689
|
|
|
function getEntityTypeSubtypeWhereSql($table, $types, $subtypes, $pairs) { |
690
|
|
|
// subtype depends upon type. |
691
|
|
|
if ($subtypes && !$types) { |
692
|
|
|
_elgg_services()->logger->warn("Cannot set subtypes without type."); |
693
|
|
|
return false; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
// short circuit if nothing is requested |
697
|
|
|
if (!$types && !$subtypes && !$pairs) { |
698
|
|
|
return ''; |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
// these are the only valid types for entities in elgg |
702
|
|
|
$valid_types = _elgg_services()->config->get('entity_types'); |
703
|
|
|
|
704
|
|
|
// pairs override |
705
|
|
|
$wheres = array(); |
706
|
|
|
if (!is_array($pairs)) { |
707
|
|
|
if (!is_array($types)) { |
708
|
|
|
$types = array($types); |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
if ($subtypes && !is_array($subtypes)) { |
712
|
|
|
$subtypes = array($subtypes); |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
// decrementer for valid types. Return false if no valid types |
716
|
|
|
$valid_types_count = count($types); |
717
|
|
|
$valid_subtypes_count = 0; |
718
|
|
|
// remove invalid types to get an accurate count of |
719
|
|
|
// valid types for the invalid subtype detection to use |
720
|
|
|
// below. |
721
|
|
|
// also grab the count of ALL subtypes on valid types to decrement later on |
722
|
|
|
// and check against. |
723
|
|
|
// |
724
|
|
|
// yes this is duplicating a foreach on $types. |
725
|
|
|
foreach ($types as $type) { |
726
|
|
|
if (!in_array($type, $valid_types)) { |
727
|
|
|
$valid_types_count--; |
728
|
|
|
unset($types[array_search($type, $types)]); |
729
|
|
|
} else { |
730
|
|
|
// do the checking (and decrementing) in the subtype section. |
731
|
|
|
$valid_subtypes_count += count($subtypes); |
732
|
|
|
} |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
// return false if nothing is valid. |
736
|
|
|
if (!$valid_types_count) { |
737
|
|
|
return false; |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
// subtypes are based upon types, so we need to look at each |
741
|
|
|
// type individually to get the right subtype id. |
742
|
|
|
foreach ($types as $type) { |
743
|
|
|
$subtype_ids = array(); |
744
|
|
|
if ($subtypes) { |
745
|
|
|
foreach ($subtypes as $subtype) { |
746
|
|
|
// check that the subtype is valid |
747
|
|
|
if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { |
748
|
|
|
// subtype value is 0 |
749
|
|
|
$subtype_ids[] = ELGG_ENTITIES_NO_VALUE; |
750
|
|
|
} elseif (!$subtype) { |
751
|
|
|
// subtype is ignored. |
752
|
|
|
// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0 |
753
|
|
|
continue; |
754
|
|
|
} else { |
755
|
|
|
$subtype_id = get_subtype_id($type, $subtype); |
756
|
|
|
|
757
|
|
|
if ($subtype_id) { |
758
|
|
|
$subtype_ids[] = $subtype_id; |
759
|
|
|
} else { |
760
|
|
|
$valid_subtypes_count--; |
761
|
|
|
_elgg_services()->logger->notice("Type-subtype '$type:$subtype' does not exist!"); |
762
|
|
|
continue; |
763
|
|
|
} |
764
|
|
|
} |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
// return false if we're all invalid subtypes in the only valid type |
768
|
|
|
if ($valid_subtypes_count <= 0) { |
769
|
|
|
return false; |
770
|
|
|
} |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
if (is_array($subtype_ids) && count($subtype_ids)) { |
774
|
|
|
$subtype_ids_str = implode(',', $subtype_ids); |
775
|
|
|
$wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; |
776
|
|
|
} else { |
777
|
|
|
$wheres[] = "({$table}.type = '$type')"; |
778
|
|
|
} |
779
|
|
|
} |
780
|
|
|
} else { |
781
|
|
|
// using type/subtype pairs |
782
|
|
|
$valid_pairs_count = count($pairs); |
783
|
|
|
$valid_pairs_subtypes_count = 0; |
784
|
|
|
|
785
|
|
|
// same deal as above--we need to know how many valid types |
786
|
|
|
// and subtypes we have before hitting the subtype section. |
787
|
|
|
// also normalize the subtypes into arrays here. |
788
|
|
|
foreach ($pairs as $paired_type => $paired_subtypes) { |
789
|
|
|
if (!in_array($paired_type, $valid_types)) { |
790
|
|
|
$valid_pairs_count--; |
791
|
|
|
unset($pairs[array_search($paired_type, $pairs)]); |
792
|
|
|
} else { |
793
|
|
|
if ($paired_subtypes && !is_array($paired_subtypes)) { |
794
|
|
|
$pairs[$paired_type] = array($paired_subtypes); |
795
|
|
|
} |
796
|
|
|
$valid_pairs_subtypes_count += count($paired_subtypes); |
797
|
|
|
} |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
if ($valid_pairs_count <= 0) { |
801
|
|
|
return false; |
802
|
|
|
} |
803
|
|
|
foreach ($pairs as $paired_type => $paired_subtypes) { |
804
|
|
|
// this will always be an array because of line 2027, right? |
805
|
|
|
// no...some overly clever person can say pair => array('object' => null) |
806
|
|
|
if (is_array($paired_subtypes)) { |
807
|
|
|
$paired_subtype_ids = array(); |
808
|
|
|
foreach ($paired_subtypes as $paired_subtype) { |
809
|
|
|
if (ELGG_ENTITIES_NO_VALUE === $paired_subtype |
810
|
|
|
|| ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) { |
811
|
|
|
|
812
|
|
|
$paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ? |
813
|
|
|
ELGG_ENTITIES_NO_VALUE : $paired_subtype_id; |
|
|
|
|
814
|
|
|
} else { |
815
|
|
|
$valid_pairs_subtypes_count--; |
816
|
|
|
_elgg_services()->logger->notice("Type-subtype '$paired_type:$paired_subtype' does not exist!"); |
817
|
|
|
// return false if we're all invalid subtypes in the only valid type |
818
|
|
|
continue; |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
|
822
|
|
|
// return false if there are no valid subtypes. |
823
|
|
|
if ($valid_pairs_subtypes_count <= 0) { |
824
|
|
|
return false; |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
|
828
|
|
|
if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) { |
829
|
|
|
$wheres[] = "({$table}.type = '$paired_type'" |
830
|
|
|
. " AND {$table}.subtype IN ($paired_subtype_ids_str))"; |
831
|
|
|
} |
832
|
|
|
} else { |
833
|
|
|
$wheres[] = "({$table}.type = '$paired_type')"; |
834
|
|
|
} |
835
|
|
|
} |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
// pairs override the above. return false if they don't exist. |
839
|
|
View Code Duplication |
if (is_array($wheres) && count($wheres)) { |
840
|
|
|
$where = implode(' OR ', $wheres); |
841
|
|
|
return "($where)"; |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
return ''; |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
/** |
848
|
|
|
* Returns SQL where clause for owner and containers. |
849
|
|
|
* |
850
|
|
|
* @param string $column Column name the guids should be checked against. Usually |
851
|
|
|
* best to provide in table.column format. |
852
|
|
|
* @param null|array $guids Array of GUIDs. |
853
|
|
|
* |
854
|
|
|
* @return false|string |
855
|
|
|
* @access private |
856
|
|
|
*/ |
857
|
|
|
function getGuidBasedWhereSql($column, $guids) { |
858
|
|
|
// short circuit if nothing requested |
859
|
|
|
// 0 is a valid guid |
860
|
|
|
if (!$guids && $guids !== 0) { |
861
|
|
|
return ''; |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
// normalize and sanitise owners |
865
|
|
|
if (!is_array($guids)) { |
866
|
|
|
$guids = array($guids); |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
$guids_sanitized = array(); |
870
|
|
|
foreach ($guids as $guid) { |
871
|
|
|
if ($guid !== ELGG_ENTITIES_NO_VALUE) { |
872
|
|
|
$guid = sanitise_int($guid); |
873
|
|
|
|
874
|
|
|
if (!$guid) { |
875
|
|
|
return false; |
876
|
|
|
} |
877
|
|
|
} |
878
|
|
|
$guids_sanitized[] = $guid; |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
$where = ''; |
882
|
|
|
$guid_str = implode(',', $guids_sanitized); |
883
|
|
|
|
884
|
|
|
// implode(',', 0) returns 0. |
885
|
|
|
if ($guid_str !== false && $guid_str !== '') { |
886
|
|
|
$where = "($column IN ($guid_str))"; |
887
|
|
|
} |
888
|
|
|
|
889
|
|
|
return $where; |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
/** |
893
|
|
|
* Returns SQL where clause for entity time limits. |
894
|
|
|
* |
895
|
|
|
* @param string $table Entity table prefix as defined in |
896
|
|
|
* SELECT...FROM entities $table |
897
|
|
|
* @param null|int $time_created_upper Time created upper limit |
898
|
|
|
* @param null|int $time_created_lower Time created lower limit |
899
|
|
|
* @param null|int $time_updated_upper Time updated upper limit |
900
|
|
|
* @param null|int $time_updated_lower Time updated lower limit |
901
|
|
|
* |
902
|
|
|
* @return false|string false on fail, string on success. |
903
|
|
|
* @access private |
904
|
|
|
*/ |
905
|
|
|
function getEntityTimeWhereSql($table, $time_created_upper = null, |
906
|
|
|
$time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) { |
907
|
|
|
|
908
|
|
|
$wheres = array(); |
909
|
|
|
|
910
|
|
|
// exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0 |
911
|
|
|
if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) { |
|
|
|
|
912
|
|
|
$wheres[] = "{$table}.time_created <= $time_created_upper"; |
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) { |
|
|
|
|
916
|
|
|
$wheres[] = "{$table}.time_created >= $time_created_lower"; |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) { |
|
|
|
|
920
|
|
|
$wheres[] = "{$table}.time_updated <= $time_updated_upper"; |
921
|
|
|
} |
922
|
|
|
|
923
|
|
|
if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) { |
|
|
|
|
924
|
|
|
$wheres[] = "{$table}.time_updated >= $time_updated_lower"; |
925
|
|
|
} |
926
|
|
|
|
927
|
|
View Code Duplication |
if (is_array($wheres) && count($wheres) > 0) { |
928
|
|
|
$where_str = implode(' AND ', $wheres); |
929
|
|
|
return "($where_str)"; |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
return ''; |
933
|
|
|
} |
934
|
|
|
|
935
|
|
|
/** |
936
|
|
|
* Gets entities based upon attributes in secondary tables. |
937
|
|
|
* Also accepts all options available to elgg_get_entities(), |
938
|
|
|
* elgg_get_entities_from_metadata(), and elgg_get_entities_from_relationship(). |
939
|
|
|
* |
940
|
|
|
* @warning requires that the entity type be specified and there can only be one |
941
|
|
|
* type. |
942
|
|
|
* |
943
|
|
|
* @see elgg_get_entities |
944
|
|
|
* @see elgg_get_entities_from_metadata |
945
|
|
|
* @see elgg_get_entities_from_relationship |
946
|
|
|
* |
947
|
|
|
* @param array $options Array in format: |
948
|
|
|
* |
949
|
|
|
* attribute_name_value_pairs => ARR ( |
950
|
|
|
* 'name' => 'name', |
951
|
|
|
* 'value' => 'value', |
952
|
|
|
* 'operand' => '=', (optional) |
953
|
|
|
* 'case_sensitive' => false (optional) |
954
|
|
|
* ) |
955
|
|
|
* If multiple values are sent via |
956
|
|
|
* an array ('value' => array('value1', 'value2') |
957
|
|
|
* the pair's operand will be forced to "IN". |
958
|
|
|
* |
959
|
|
|
* attribute_name_value_pairs_operator => null|STR The operator to use for combining |
960
|
|
|
* (name = value) OPERATOR (name = value); default is AND |
961
|
|
|
* |
962
|
|
|
* @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors. |
963
|
|
|
* @throws InvalidArgumentException |
964
|
|
|
* @todo Does not support ordering by attributes or using an attribute pair shortcut like this ('title' => 'foo') |
965
|
|
|
*/ |
966
|
|
|
function getEntitiesFromAttributes(array $options = array()) { |
967
|
|
|
$defaults = array( |
968
|
|
|
'attribute_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, |
969
|
|
|
'attribute_name_value_pairs_operator' => 'AND', |
970
|
|
|
); |
971
|
|
|
|
972
|
|
|
$options = array_merge($defaults, $options); |
973
|
|
|
|
974
|
|
|
$singulars = array('type', 'attribute_name_value_pair'); |
975
|
|
|
$options = _elgg_normalize_plural_options_array($options, $singulars); |
976
|
|
|
|
977
|
|
|
$clauses = _elgg_get_entity_attribute_where_sql($options); |
978
|
|
|
|
979
|
|
View Code Duplication |
if ($clauses) { |
980
|
|
|
// merge wheres to pass to elgg_get_entities() |
981
|
|
|
if (isset($options['wheres']) && !is_array($options['wheres'])) { |
982
|
|
|
$options['wheres'] = array($options['wheres']); |
983
|
|
|
} elseif (!isset($options['wheres'])) { |
984
|
|
|
$options['wheres'] = array(); |
985
|
|
|
} |
986
|
|
|
|
987
|
|
|
$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); |
988
|
|
|
|
989
|
|
|
// merge joins to pass to elgg_get_entities() |
990
|
|
|
if (isset($options['joins']) && !is_array($options['joins'])) { |
991
|
|
|
$options['joins'] = array($options['joins']); |
992
|
|
|
} elseif (!isset($options['joins'])) { |
993
|
|
|
$options['joins'] = array(); |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
$options['joins'] = array_merge($options['joins'], $clauses['joins']); |
997
|
|
|
} |
998
|
|
|
|
999
|
|
|
return elgg_get_entities_from_relationship($options); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
/** |
1003
|
|
|
* Get the join and where clauses for working with entity attributes |
1004
|
|
|
* |
1005
|
|
|
* @return false|array False on fail, array('joins', 'wheres') |
1006
|
|
|
* @access private |
1007
|
|
|
* @throws InvalidArgumentException |
1008
|
|
|
*/ |
1009
|
|
|
function getEntityAttributeWhereSql(array $options = array()) { |
1010
|
|
|
|
1011
|
|
|
if (!isset($options['types'])) { |
1012
|
|
|
throw new \InvalidArgumentException("The entity type must be defined for elgg_get_entities_from_attributes()"); |
1013
|
|
|
} |
1014
|
|
|
|
1015
|
|
|
if (is_array($options['types']) && count($options['types']) !== 1) { |
1016
|
|
|
throw new \InvalidArgumentException("Only one type can be passed to elgg_get_entities_from_attributes()"); |
1017
|
|
|
} |
1018
|
|
|
|
1019
|
|
|
// type can be passed as string or array |
1020
|
|
|
$type = $options['types']; |
1021
|
|
|
if (is_array($type)) { |
1022
|
|
|
$type = $type[0]; |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
|
|
// @todo the types should be defined somewhere (as constant on \ElggEntity?) |
1026
|
|
|
if (!in_array($type, array('group', 'object', 'site', 'user'))) { |
1027
|
|
|
throw new \InvalidArgumentException("Invalid type '$type' passed to elgg_get_entities_from_attributes()"); |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
|
1031
|
|
|
$type_table = "{$this->CONFIG->dbprefix}{$type}s_entity"; |
1032
|
|
|
|
1033
|
|
|
$return = array( |
1034
|
|
|
'joins' => array(), |
1035
|
|
|
'wheres' => array(), |
1036
|
|
|
); |
1037
|
|
|
|
1038
|
|
|
// short circuit if nothing requested |
1039
|
|
|
if ($options['attribute_name_value_pairs'] == ELGG_ENTITIES_ANY_VALUE) { |
1040
|
|
|
return $return; |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
|
|
if (!is_array($options['attribute_name_value_pairs'])) { |
1044
|
|
|
throw new \InvalidArgumentException("attribute_name_value_pairs must be an array for elgg_get_entities_from_attributes()"); |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
$wheres = array(); |
1048
|
|
|
|
1049
|
|
|
// check if this is an array of pairs or just a single pair. |
1050
|
|
|
$pairs = $options['attribute_name_value_pairs']; |
1051
|
|
|
if (isset($pairs['name']) || isset($pairs['value'])) { |
1052
|
|
|
$pairs = array($pairs); |
1053
|
|
|
} |
1054
|
|
|
|
1055
|
|
|
$pair_wheres = array(); |
1056
|
|
|
foreach ($pairs as $index => $pair) { |
1057
|
|
|
// must have at least a name and value |
1058
|
|
|
if (!isset($pair['name']) || !isset($pair['value'])) { |
1059
|
|
|
continue; |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
if (isset($pair['operand'])) { |
1063
|
|
|
$operand = sanitize_string($pair['operand']); |
1064
|
|
|
} else { |
1065
|
|
|
$operand = '='; |
1066
|
|
|
} |
1067
|
|
|
|
1068
|
|
|
if (is_numeric($pair['value'])) { |
1069
|
|
|
$value = sanitize_string($pair['value']); |
1070
|
|
|
} else if (is_array($pair['value'])) { |
1071
|
|
|
$values_array = array(); |
1072
|
|
|
foreach ($pair['value'] as $pair_value) { |
1073
|
|
|
if (is_numeric($pair_value)) { |
1074
|
|
|
$values_array[] = sanitize_string($pair_value); |
1075
|
|
|
} else { |
1076
|
|
|
$values_array[] = "'" . sanitize_string($pair_value) . "'"; |
1077
|
|
|
} |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
$operand = 'IN'; |
1081
|
|
|
if ($values_array) { |
|
|
|
|
1082
|
|
|
$value = '(' . implode(', ', $values_array) . ')'; |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
|
|
} else { |
1086
|
|
|
$value = "'" . sanitize_string($pair['value']) . "'"; |
1087
|
|
|
} |
1088
|
|
|
|
1089
|
|
|
$name = sanitize_string($pair['name']); |
1090
|
|
|
|
1091
|
|
|
// case sensitivity can be specified per pair |
1092
|
|
|
$pair_binary = ''; |
1093
|
|
|
if (isset($pair['case_sensitive'])) { |
1094
|
|
|
$pair_binary = ($pair['case_sensitive']) ? 'BINARY ' : ''; |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
$pair_wheres[] = "({$pair_binary}type_table.$name $operand $value)"; |
|
|
|
|
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
if ($where = implode(" {$options['attribute_name_value_pairs_operator']} ", $pair_wheres)) { |
1101
|
|
|
$return['wheres'][] = "($where)"; |
1102
|
|
|
|
1103
|
|
|
$return['joins'][] = "JOIN $type_table type_table ON e.guid = type_table.guid"; |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
return $return; |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
/** |
1110
|
|
|
* Returns a list of months in which entities were updated or created. |
1111
|
|
|
* |
1112
|
|
|
* @tip Use this to generate a list of archives by month for when entities were added or updated. |
1113
|
|
|
* |
1114
|
|
|
* @todo document how to pass in array for $subtype |
1115
|
|
|
* |
1116
|
|
|
* @warning Months are returned in the form YYYYMM. |
1117
|
|
|
* |
1118
|
|
|
* @param string $type The type of entity |
1119
|
|
|
* @param string $subtype The subtype of entity |
1120
|
|
|
* @param int $container_guid The container GUID that the entities belong to |
1121
|
|
|
* @param int $site_guid The site GUID |
1122
|
|
|
* @param string $order_by Order_by SQL order by clause |
1123
|
|
|
* |
1124
|
|
|
* @return array|false Either an array months as YYYYMM, or false on failure |
1125
|
|
|
*/ |
1126
|
|
|
function getDates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, |
1127
|
|
|
$order_by = 'time_created') { |
1128
|
|
|
|
1129
|
|
|
|
1130
|
|
|
|
1131
|
|
|
$site_guid = (int) $site_guid; |
1132
|
|
|
if ($site_guid == 0) { |
1133
|
|
|
$site_guid = $this->CONFIG->site_guid; |
1134
|
|
|
} |
1135
|
|
|
$where = array(); |
1136
|
|
|
|
1137
|
|
|
if ($type != "") { |
1138
|
|
|
$type = sanitise_string($type); |
1139
|
|
|
$where[] = "type='$type'"; |
1140
|
|
|
} |
1141
|
|
|
|
1142
|
|
|
if (is_array($subtype)) { |
1143
|
|
|
$tempwhere = ""; |
1144
|
|
View Code Duplication |
if (sizeof($subtype)) { |
1145
|
|
|
foreach ($subtype as $typekey => $subtypearray) { |
1146
|
|
|
foreach ($subtypearray as $subtypeval) { |
1147
|
|
|
$typekey = sanitise_string($typekey); |
1148
|
|
|
if (!empty($subtypeval)) { |
1149
|
|
|
if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { |
1150
|
|
|
return false; |
1151
|
|
|
} |
1152
|
|
|
} else { |
1153
|
|
|
$subtypeval = 0; |
1154
|
|
|
} |
1155
|
|
|
if (!empty($tempwhere)) { |
1156
|
|
|
$tempwhere .= " or "; |
1157
|
|
|
} |
1158
|
|
|
$tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})"; |
1159
|
|
|
} |
1160
|
|
|
} |
1161
|
|
|
} |
1162
|
|
|
if (!empty($tempwhere)) { |
1163
|
|
|
$where[] = "({$tempwhere})"; |
1164
|
|
|
} |
1165
|
|
|
} else { |
1166
|
|
|
if ($subtype) { |
1167
|
|
|
if (!$subtype_id = get_subtype_id($type, $subtype)) { |
1168
|
|
|
return false; |
1169
|
|
|
} else { |
1170
|
|
|
$where[] = "subtype=$subtype_id"; |
1171
|
|
|
} |
1172
|
|
|
} |
1173
|
|
|
} |
1174
|
|
|
|
1175
|
|
View Code Duplication |
if ($container_guid !== 0) { |
1176
|
|
|
if (is_array($container_guid)) { |
1177
|
|
|
foreach ($container_guid as $key => $val) { |
1178
|
|
|
$container_guid[$key] = (int) $val; |
1179
|
|
|
} |
1180
|
|
|
$where[] = "container_guid in (" . implode(",", $container_guid) . ")"; |
1181
|
|
|
} else { |
1182
|
|
|
$container_guid = (int) $container_guid; |
1183
|
|
|
$where[] = "container_guid = {$container_guid}"; |
1184
|
|
|
} |
1185
|
|
|
} |
1186
|
|
|
|
1187
|
|
|
if ($site_guid > 0) { |
1188
|
|
|
$where[] = "site_guid = {$site_guid}"; |
1189
|
|
|
} |
1190
|
|
|
|
1191
|
|
|
$where[] = _elgg_get_access_where_sql(array('table_alias' => '')); |
1192
|
|
|
|
1193
|
|
|
$sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth |
1194
|
|
|
FROM {$this->CONFIG->dbprefix}entities where "; |
1195
|
|
|
|
1196
|
|
|
foreach ($where as $w) { |
1197
|
|
|
$sql .= " $w and "; |
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
$sql .= "1=1 ORDER BY $order_by"; |
1201
|
|
|
if ($result = _elgg_services()->db->getData($sql)) { |
1202
|
|
|
$endresult = array(); |
1203
|
|
|
foreach ($result as $res) { |
1204
|
|
|
$endresult[] = $res->yearmonth; |
1205
|
|
|
} |
1206
|
|
|
return $endresult; |
1207
|
|
|
} |
1208
|
|
|
return false; |
1209
|
|
|
} |
1210
|
|
|
|
1211
|
|
|
/** |
1212
|
|
|
* Update the last_action column in the entities table for $guid. |
1213
|
|
|
* |
1214
|
|
|
* @warning This is different to time_updated. Time_updated is automatically set, |
1215
|
|
|
* while last_action is only set when explicitly called. |
1216
|
|
|
* |
1217
|
|
|
* @param int $guid Entity annotation|relationship action carried out on |
1218
|
|
|
* @param int $posted Timestamp of last action |
1219
|
|
|
* |
1220
|
|
|
* @return bool |
1221
|
|
|
* @access private |
1222
|
|
|
*/ |
1223
|
|
|
function updateLastAction($guid, $posted = null) { |
1224
|
|
|
|
1225
|
|
|
$guid = (int)$guid; |
1226
|
|
|
$posted = (int)$posted; |
1227
|
|
|
|
1228
|
|
|
if (!$posted) { |
1229
|
|
|
$posted = time(); |
1230
|
|
|
} |
1231
|
|
|
|
1232
|
|
|
if ($guid) { |
1233
|
|
|
//now add to the river updated table |
1234
|
|
|
$query = "UPDATE {$this->CONFIG->dbprefix}entities SET last_action = {$posted} WHERE guid = {$guid}"; |
1235
|
|
|
$result = _elgg_services()->db->updateData($query); |
1236
|
|
|
if ($result) { |
1237
|
|
|
return true; |
1238
|
|
|
} else { |
1239
|
|
|
return false; |
1240
|
|
|
} |
1241
|
|
|
} else { |
1242
|
|
|
return false; |
1243
|
|
|
} |
1244
|
|
|
} |
1245
|
|
|
} |
1246
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.