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