Passed
Push — master ( f13f78...5c1b24 )
by Ismayil
04:22
created

engine/classes/Elgg/Database/EntityTable.php (1 issue)

Upgrade to new PHP Analysis Engine

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 197 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 197
		$this->config = $config;
123 197
		$this->db = $db;
124 197
		$this->table = $this->db->prefix . 'entities';
125 197
		$this->entity_cache = $entity_cache;
126 197
		$this->metadata_cache = $metadata_cache;
127 197
		$this->subtype_table = $subtype_table;
128 197
		$this->events = $events;
129 197
		$this->session = $session;
130 197
		$this->translator = $translator;
131 197
		$this->logger = $logger;
132 197
	}
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 262
	public function getRow($guid, $user_guid = null) {
152
153 262
		if (!$guid) {
154
			return false;
155
		}
156
157 262
		$access = _elgg_get_access_where_sql([
158 262
			'table_alias' => '',
159 262
			'user_guid' => $user_guid,
160
		]);
161
162 262
		$sql = "SELECT * FROM {$this->db->prefix}entities
163 262
			WHERE guid = :guid AND $access";
164
165
		$params = [
166 262
			':guid' => (int) $guid,
167
		];
168
169 262
		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 6
	public function insertRow(stdClass $row, array $attributes = []) {
183
184 6
		$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 6
		return $this->db->insertData($sql, [
192 6
			':type' => $row->type,
193 6
			':subtype_id' => $row->subtype_id,
194 6
			':owner_guid' => $row->owner_guid,
195 6
			':container_guid' => $row->container_guid,
196 6
			':access_id' => $row->access_id,
197 6
			':time_created' => $row->time_created,
198 6
			':time_updated' => $row->time_updated,
199 6
			':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 1
	public function updateRow($guid, stdClass $row) {
211
		$sql = "
212 1
			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 1
			':owner_guid' => $row->owner_guid,
223 1
			':access_id' => $row->access_id,
224 1
			':container_guid' => $row->container_guid,
225 1
			':time_created' => $row->time_created,
226 1
			':time_updated' => $row->time_updated,
227 1
			':guid' => $guid,
228
		];
229
230 1
		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 262
	public function rowToElggStar($row) {
250 262
		if (!$row instanceof stdClass) {
251
			return $row;
252
		}
253
254 262
		if (!isset($row->guid) || !isset($row->subtype)) {
255
			return $row;
256
		}
257
	
258 262
		$class_name = $this->subtype_table->getClassFromId($row->subtype);
259 262
		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 262
		if (!$class_name) {
265
			$map = [
266 262
				'object' => ElggObject::class,
267
				'user' => ElggUser::class,
268
				'group' => ElggGroup::class,
269
				'site' => ElggSite::class,
270
			];
271
272 262
			if (isset($map[$row->type])) {
273 262
				$class_name = $map[$row->type];
274
			} else {
275
				throw new InstallationException("Entity type {$row->type} is not supported.");
276
			}
277
		}
278
279 262
		$entity = new $class_name($row);
280 262
		if (!$entity instanceof ElggEntity) {
281
			throw new ClassException("$class_name must extend " . ElggEntity::class);
282
		}
283
284 262
		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 262
	protected function getFromCache($guid) {
295 262
		$entity = $this->entity_cache->get($guid);
296 262
		if ($entity) {
297 183
			return $entity;
298
		}
299
300 262
		$memcache = _elgg_get_memcache('new_entity_cache');
301 262
		$entity = $memcache->load($guid);
302 262
		if (!$entity instanceof ElggEntity) {
303 262
			return false;
304
		}
305
306
		// Validate accessibility if from memcache
307
		if (!elgg_get_ignore_access() && !has_access_to_entity($entity)) {
308
			return false;
309
		}
310
311
		$this->entity_cache->set($entity);
312
		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 262
	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 262
		if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
331 38
			return false;
332
		}
333
334 262
		$guid = (int) $guid;
335
336 262
		$entity = $this->getFromCache($guid);
337 262
		if ($entity && (!$type || elgg_instanceof($entity, $type))) {
338 181
			return $entity;
339
		}
340
341 262
		$row = $this->getRow($guid);
342 262
		if (!$row) {
343 262
			return false;
344
		}
345
346 54
		if ($type && $row->type != $type) {
347
			return false;
348
		}
349
350 54
		$entity = $this->rowToElggStar($row);
351
352 54
		if ($entity instanceof ElggEntity) {
353 54
			$entity->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
354
		}
355
356 54
		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 5
	public function exists($guid) {
371
372
		// need to ignore access and show hidden entities to check existence
373 5
		$ia = $this->session->setIgnoreAccess(true);
374 5
		$show_hidden = access_show_hidden_entities(true);
375
376 5
		$result = $this->getRow($guid);
377
378 5
		$this->session->setIgnoreAccess($ia);
379 5
		access_show_hidden_entities($show_hidden);
380
381 5
		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 3
	public function getEntities(array $options = []) {
504 3
		_elgg_check_unsupported_site_guid($options);
505
506
		$defaults = [
507 3
			'types'                 => ELGG_ENTITIES_ANY_VALUE,
508 3
			'subtypes'              => ELGG_ENTITIES_ANY_VALUE,
509 3
			'type_subtype_pairs'    => ELGG_ENTITIES_ANY_VALUE,
510
511 3
			'guids'                 => ELGG_ENTITIES_ANY_VALUE,
512 3
			'owner_guids'           => ELGG_ENTITIES_ANY_VALUE,
513 3
			'container_guids'       => ELGG_ENTITIES_ANY_VALUE,
514
515 3
			'modified_time_lower'   => ELGG_ENTITIES_ANY_VALUE,
516 3
			'modified_time_upper'   => ELGG_ENTITIES_ANY_VALUE,
517 3
			'created_time_lower'    => ELGG_ENTITIES_ANY_VALUE,
518 3
			'created_time_upper'    => ELGG_ENTITIES_ANY_VALUE,
519
520
			'reverse_order_by'      => false,
521 3
			'order_by'              => 'e.time_created desc',
522 3
			'group_by'              => ELGG_ENTITIES_ANY_VALUE,
523 3
			'limit'                 => $this->config->default_limit,
524 3
			'offset'                => 0,
525
			'count'                 => false,
526
			'selects'               => [],
527
			'wheres'                => [],
528
			'joins'                 => [],
529
530
			'preload_owners'        => false,
531
			'preload_containers'    => false,
532 3
			'callback'              => 'entity_row_to_elggstar',
533
			'distinct'              => true,
534
535
			'batch'                 => false,
536
			'batch_inc_offset'      => true,
537 3
			'batch_size'            => 25,
538
539
			// private API
540
			'__ElggBatch'           => null,
541
		];
542
543 3
		$options = array_merge($defaults, $options);
544
545 3 View Code Duplication
		if ($options['batch'] && !$options['count']) {
546
			$batch_size = $options['batch_size'];
547
			$batch_inc_offset = $options['batch_inc_offset'];
548
549
			// clean batch keys from $options.
550
			unset($options['batch'], $options['batch_size'], $options['batch_inc_offset']);
551
552
			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 3 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 3
		$singulars = ['type', 'subtype', 'guid', 'owner_guid', 'container_guid'];
567 3
		$options = _elgg_normalize_plural_options_array($options, $singulars);
568
569 3
		$options = $this->autoJoinTables($options);
570
571
		// evaluate where clauses
572 3
		if (!is_array($options['wheres'])) {
573
			$options['wheres'] = [$options['wheres']];
574
		}
575
576 3
		$wheres = $options['wheres'];
577
578 3
		$wheres[] = $this->getEntityTypeSubtypeWhereSql('e', $options['types'],
579 3
			$options['subtypes'], $options['type_subtype_pairs']);
580
581 3
		$wheres[] = $this->getGuidBasedWhereSql('e.guid', $options['guids']);
582 3
		$wheres[] = $this->getGuidBasedWhereSql('e.owner_guid', $options['owner_guids']);
583 3
		$wheres[] = $this->getGuidBasedWhereSql('e.container_guid', $options['container_guids']);
584
585 3
		$wheres[] = $this->getEntityTimeWhereSql('e', $options['created_time_upper'],
586 3
			$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 3 View Code Duplication
		foreach ($wheres as $i => $where) {
591 3
			if ($where === false) {
592 1
				return false;
593
			} elseif (empty($where)) {
594 3
				unset($wheres[$i]);
595
			}
596
		}
597
598
		// remove identical where clauses
599 2
		$wheres = array_unique($wheres);
600
601
		// evaluate join clauses
602 2 View Code Duplication
		if (!is_array($options['joins'])) {
603
			$options['joins'] = [$options['joins']];
604
		}
605
606
		// remove identical join clauses
607 2
		$joins = array_unique($options['joins']);
608
609 2 View Code Duplication
		foreach ($joins as $i => $join) {
610
			if ($join === false) {
611
				return false;
612
			} elseif (empty($join)) {
613
				unset($joins[$i]);
614
			}
615
		}
616
617
		// evalutate selects
618 2
		if ($options['selects']) {
619
			$selects = '';
620
			foreach ($options['selects'] as $select) {
621
				$selects .= ", $select";
622
			}
623
		} else {
624 2
			$selects = '';
625
		}
626
627 2
		if (!$options['count']) {
628 2
			$distinct = $options['distinct'] ? "DISTINCT" : "";
629 2
			$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
			$count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*";
633
			$query = "SELECT COUNT($count_expr) as total FROM {$this->db->prefix}entities e ";
634
		}
635
636
		// add joins
637 2
		foreach ($joins as $j) {
638
			$query .= " $j ";
639
		}
640
641
		// add wheres
642 2
		$query .= ' WHERE ';
643
644 2
		foreach ($wheres as $w) {
645 2
			$query .= " $w AND ";
646
		}
647
648
		// Add access controls
649 2
		$query .= _elgg_get_access_where_sql();
650
651
		// reverse order by
652 2
		if ($options['reverse_order_by']) {
653
			$options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']);
654
		}
655
656 2
		if ($options['count']) {
657
			$total = $this->db->getDataRow($query);
658
			return (int) $total->total;
659
		}
660
661 2
		if ($options['group_by']) {
662
			$query .= " GROUP BY {$options['group_by']}";
663
		}
664
665 2
		if ($options['order_by']) {
666 2
			$query .= " ORDER BY {$options['order_by']}";
667
		}
668
669 2 View Code Duplication
		if ($options['limit']) {
670 2
			$limit = sanitise_int($options['limit'], false);
671 2
			$offset = sanitise_int($options['offset'], false);
672 2
			$query .= " LIMIT $offset, $limit";
673
		}
674
675 2
		if ($options['callback'] === 'entity_row_to_elggstar') {
676 2
			$results = $this->fetchFromSql($query, $options['__ElggBatch']);
677
		} else {
678
			$results = $this->db->getData($query, $options['callback']);
679
		}
680
681 2
		if (!$results) {
682
			// no results, no preloading
683 2
			return $results;
684
		}
685
686
		// populate entity and metadata caches, and prepare $entities for preloader
687
		$guids = [];
688
		foreach ($results as $item) {
689
			// A custom callback could result in items that aren't \ElggEntity's, so check for them
690
			if ($item instanceof ElggEntity) {
691
				$this->entity_cache->set($item);
692
				// plugins usually have only settings
693
				if (!$item instanceof ElggPlugin) {
694
					$guids[] = $item->guid;
695
				}
696
			}
697
		}
698
		// @todo Without this, recursive delete fails. See #4568
699
		reset($results);
700
701
		if ($guids) {
702
			// there were entities in the result set, preload metadata for them
703
			$this->metadata_cache->populateFromEntities($guids);
704
		}
705
706
		if (count($results) > 1) {
707
			$props_to_preload = [];
708
			if ($options['preload_owners']) {
709
				$props_to_preload[] = 'owner_guid';
710
			}
711
			if ($options['preload_containers']) {
712
				$props_to_preload[] = 'container_guid';
713
			}
714
			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
				_elgg_services()->entityPreloader->preload($results, $props_to_preload);
719
			}
720
		}
721
722
		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 3
	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 3
		if (!is_array($options['types'])
736 1
				|| count($options['types']) !== 1
737 1
				|| !empty($options['selects'])
738 1
				|| !empty($options['wheres'])
739
				|| !empty($options['joins'])
740
				|| $options['callback'] !== 'entity_row_to_elggstar'
741 3
				|| $options['count']) {
742
			// Too dangerous to auto-join
743 3
			return $options;
744
		}
745
746
		$join_types = [
747
			// Each class must have a static getExternalAttributes() : array
748
			'object' => 'ElggObject',
749
			'user' => 'ElggUser',
750
			'group' => 'ElggGroup',
751
			'site' => 'ElggSite',
752
		];
753
754
		// We use reset() because $options['types'] may not have a numeric key
755
		$type = reset($options['types']);
756
757
		// Get the columns we'll need to select. We can't use st.* because the order_by
758
		// clause may reference "guid", which MySQL will complain about being ambiguous
759
		try {
760
			$attributes = \ElggEntity::getExtraAttributeDefaults($type);
761
		} catch (\Exception $e) {
762
			$this->logger->error("Unrecognized type: $type");
763
			return $options;
764
		}
765
766
		foreach (array_keys($attributes) as $col) {
767
			$options['selects'][] = "st.$col";
768
		}
769
770
		// join the secondary table
771
		$options['joins'][] = "JOIN {$this->db->prefix}{$type}s_entity st ON (e.guid = st.guid)";
772
773
		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 2
	public function fetchFromSql($sql, \ElggBatch $batch = null) {
787 2
		$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 2
			'object' => 'title',
794
			'user' => 'password_hash',
795
			'group' => 'name',
796
			'site' => 'url',
797
		];
798
799 2
		$rows = $this->db->getData($sql);
800
801
		// guids to look up in each type
802 2
		$lookup_types = [];
803
		// maps GUIDs to the $rows key
804 2
		$guid_to_key = [];
805
806 2
		if (isset($rows[0]->type, $rows[0]->subtype)
807 2
				&& $rows[0]->type === 'object'
808 2
				&& $rows[0]->subtype == $plugin_subtype) {
809
			// Likely the entire resultset is plugins, which have already been optimized
810
			// to JOIN the secondary table. In this case we allow retrieving from cache,
811
			// but abandon the extra queries.
812
			$types_to_optimize = [];
813
		}
814
815
		// First pass: use cache where possible, gather GUIDs that we're optimizing
816 2
		foreach ($rows as $i => $row) {
817
			if (empty($row->guid) || empty($row->type)) {
818
				throw new LogicException('Entity row missing guid or type');
819
			}
820
821
			// We try ephemeral cache because it's blazingly fast and we ideally want to access
822
			// the same PHP instance. We don't try memcache because it isn't worth the overhead.
823
			$entity = $this->entity_cache->get($row->guid);
824
			if ($entity) {
825
				// from static var, must be refreshed in case row has extra columns
826
				$entity->refresh($row);
827
				$rows[$i] = $entity;
828
				continue;
829
			}
830
831
			if (isset($types_to_optimize[$row->type])) {
832
				// check if row already looks JOINed.
833
				if (isset($row->{$types_to_optimize[$row->type]})) {
834
					// Row probably already contains JOINed secondary table. Don't make another query just
835
					// to pull data that's already there
836
					continue;
837
				}
838
				$lookup_types[$row->type][] = $row->guid;
839
				$guid_to_key[$row->guid] = $i;
840
			}
841
		}
842
		// Do secondary queries and merge rows
843 2
		if ($lookup_types) {
844
			foreach ($lookup_types as $type => $guids) {
845
				$set = "(" . implode(',', $guids) . ")";
846
				$sql = "SELECT * FROM {$this->db->prefix}{$type}s_entity WHERE guid IN $set";
847
				$secondary_rows = $this->db->getData($sql);
848
				if ($secondary_rows) {
849
					foreach ($secondary_rows as $secondary_row) {
850
						$key = $guid_to_key[$secondary_row->guid];
851
						// cast to arrays to merge then cast back
852
						$rows[$key] = (object) array_merge((array) $rows[$key], (array) $secondary_row);
853
					}
854
				}
855
			}
856
		}
857
		// Second pass to finish conversion
858 2
		foreach ($rows as $i => $row) {
859
			if ($row instanceof ElggEntity) {
860
				continue;
861
			} else {
862
				try {
863
					$rows[$i] = $this->rowToElggStar($row);
864
				} catch (IncompleteEntityException $e) {
865
					// don't let incomplete entities throw fatal errors
866
					unset($rows[$i]);
867
868
					// report incompletes to the batch process that spawned this query
869
					if ($batch) {
870
						$batch->reportIncompleteEntity($row);
871
					}
872
				}
873
			}
874
		}
875 2
		return $rows;
876
	}
877
878
	/**
879
	 * Returns SQL where clause for type and subtype on main entity table
880
	 *
881
	 * @param string     $table    Entity table prefix as defined in SELECT...FROM entities $table
882
	 * @param null|array $types    Array of types or null if none.
883
	 * @param null|array $subtypes Array of subtypes or null if none
884
	 * @param null|array $pairs    Array of pairs of types and subtypes
885
	 *
886
	 * @return false|string
887
	 * @access private
888
	 */
889 99
	public function getEntityTypeSubtypeWhereSql($table, $types, $subtypes, $pairs) {
890
		// subtype depends upon type.
891 99
		if ($subtypes && !$types) {
892
			$this->logger->warn("Cannot set subtypes without type.");
893
			return false;
894
		}
895
896
		// short circuit if nothing is requested
897 99
		if (!$types && !$subtypes && !$pairs) {
898 98
			return '';
899
		}
900
901
		// pairs override
902 1
		$wheres = [];
903 1
		if (!is_array($pairs)) {
904 1
			if (!is_array($types)) {
905
				$types = [$types];
906
			}
907
908 1
			if ($subtypes && !is_array($subtypes)) {
909
				$subtypes = [$subtypes];
910
			}
911
912
			// decrementer for valid types.  Return false if no valid types
913 1
			$valid_types_count = count($types);
914 1
			$valid_subtypes_count = 0;
915
			// remove invalid types to get an accurate count of
916
			// valid types for the invalid subtype detection to use
917
			// below.
918
			// also grab the count of ALL subtypes on valid types to decrement later on
919
			// and check against.
920
			//
921
			// yes this is duplicating a foreach on $types.
922 1
			foreach ($types as $type) {
923 1
				if (!in_array($type, \Elgg\Config::getEntityTypes())) {
924
					$valid_types_count--;
925
					unset($types[array_search($type, $types)]);
926
				} else {
927
					// do the checking (and decrementing) in the subtype section.
928 1
					$valid_subtypes_count += count($subtypes);
929
				}
930
			}
931
932
			// return false if nothing is valid.
933 1
			if (!$valid_types_count) {
934
				return false;
935
			}
936
937
			// subtypes are based upon types, so we need to look at each
938
			// type individually to get the right subtype id.
939 1
			foreach ($types as $type) {
940 1
				$subtype_ids = [];
941 1
				if ($subtypes) {
942 1
					foreach ($subtypes as $subtype) {
943
						// check that the subtype is valid
944 1
						if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
945
							// subtype value is 0
946
							$subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
947 1
						} elseif (!$subtype) {
948
							// subtype is ignored.
949
							// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
950
							continue;
951
						} else {
952 1
							$subtype_id = get_subtype_id($type, $subtype);
953
954 1
							if ($subtype_id) {
955
								$subtype_ids[] = $subtype_id;
956
							} else {
957 1
								$valid_subtypes_count--;
958 1
								$this->logger->warn("Type-subtype '$type:$subtype' does not exist!");
959 1
								continue;
960
							}
961
						}
962
					}
963
964
					// return false if we're all invalid subtypes in the only valid type
965 1
					if ($valid_subtypes_count <= 0) {
966 1
						return false;
967
					}
968
				}
969
970
				if (is_array($subtype_ids) && count($subtype_ids)) {
971
					$subtype_ids_str = implode(',', $subtype_ids);
972
					$wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
973
				} else {
974
					$wheres[] = "({$table}.type = '$type')";
975
				}
976
			}
977
		} else {
978
			// using type/subtype pairs
979
			$valid_pairs_count = count($pairs);
980
			$valid_pairs_subtypes_count = 0;
981
982
			// same deal as above--we need to know how many valid types
983
			// and subtypes we have before hitting the subtype section.
984
			// also normalize the subtypes into arrays here.
985
			foreach ($pairs as $paired_type => $paired_subtypes) {
986
				if (!in_array($paired_type, \Elgg\Config::getEntityTypes())) {
987
					$valid_pairs_count--;
988
					unset($pairs[array_search($paired_type, $pairs)]);
989
				} else {
990
					if ($paired_subtypes && !is_array($paired_subtypes)) {
991
						$pairs[$paired_type] = [$paired_subtypes];
992
					}
993
					$valid_pairs_subtypes_count += count($paired_subtypes);
994
				}
995
			}
996
997
			if ($valid_pairs_count <= 0) {
998
				return false;
999
			}
1000
			foreach ($pairs as $paired_type => $paired_subtypes) {
1001
				// this will always be an array because of line 2027, right?
1002
				// no...some overly clever person can say pair => array('object' => null)
1003
				if (is_array($paired_subtypes)) {
1004
					$paired_subtype_ids = [];
1005
					foreach ($paired_subtypes as $paired_subtype) {
1006
						if (ELGG_ENTITIES_NO_VALUE === $paired_subtype || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
1007
							$paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
1008
									ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
1009
						} else {
1010
							$valid_pairs_subtypes_count--;
1011
							$this->logger->warn("Type-subtype '$paired_type:$paired_subtype' does not exist!");
1012
							// return false if we're all invalid subtypes in the only valid type
1013
							continue;
1014
						}
1015
					}
1016
1017
					// return false if there are no valid subtypes.
1018
					if ($valid_pairs_subtypes_count <= 0) {
1019
						return false;
1020
					}
1021
1022
1023
					if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
1024
						$wheres[] = "({$table}.type = '$paired_type'"
1025
								. " AND {$table}.subtype IN ($paired_subtype_ids_str))";
1026
					}
1027
				} else {
1028
					$wheres[] = "({$table}.type = '$paired_type')";
1029
				}
1030
			}
1031
		}
1032
1033
		// pairs override the above.  return false if they don't exist.
1034 View Code Duplication
		if (is_array($wheres) && count($wheres)) {
1035
			$where = implode(' OR ', $wheres);
1036
			return "($where)";
1037
		}
1038
1039
		return '';
1040
	}
1041
1042
	/**
1043
	 * Returns SQL where clause for owner and containers.
1044
	 *
1045
	 * @param string     $column Column name the guids should be checked against. Usually
1046
	 *                           best to provide in table.column format.
1047
	 * @param null|array $guids  Array of GUIDs.
1048
	 *
1049
	 * @return false|string
1050
	 * @access private
1051
	 */
1052 99
	public function getGuidBasedWhereSql($column, $guids) {
1053
		// short circuit if nothing requested
1054
		// 0 is a valid guid
1055 99
		if (!$guids && $guids !== 0) {
1056 99
			return '';
1057
		}
1058
1059
		// normalize and sanitise owners
1060 2
		if (!is_array($guids)) {
1061
			$guids = [$guids];
1062
		}
1063
1064 2
		$guids_sanitized = [];
1065 2
		foreach ($guids as $guid) {
1066 2
			if ($guid !== ELGG_ENTITIES_NO_VALUE) {
1067 2
				$guid = sanitise_int($guid);
1068
1069 2
				if (!$guid) {
1070
					return false;
1071
				}
1072
			}
1073 2
			$guids_sanitized[] = $guid;
1074
		}
1075
1076 2
		$where = '';
1077 2
		$guid_str = implode(',', $guids_sanitized);
1078
1079
		// implode(',', 0) returns 0.
1080 2
		if ($guid_str !== false && $guid_str !== '') {
1081 2
			$where = "($column IN ($guid_str))";
1082
		}
1083
1084 2
		return $where;
1085
	}
1086
1087
	/**
1088
	 * Returns SQL where clause for entity time limits.
1089
	 *
1090
	 * @param string   $table              Entity table prefix as defined in
1091
	 *                                     SELECT...FROM entities $table
1092
	 * @param null|int $time_created_upper Time created upper limit
1093
	 * @param null|int $time_created_lower Time created lower limit
1094
	 * @param null|int $time_updated_upper Time updated upper limit
1095
	 * @param null|int $time_updated_lower Time updated lower limit
1096
	 *
1097
	 * @return false|string false on fail, string on success.
1098
	 * @access private
1099
	 */
1100 99
	public function getEntityTimeWhereSql($table, $time_created_upper = null,
1101
	$time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) {
1102
1103 99
		$wheres = [];
1104
1105
		// exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
1106 99
		if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
1107
			$wheres[] = "{$table}.time_created <= $time_created_upper";
1108
		}
1109
1110 99
		if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
1111
			$wheres[] = "{$table}.time_created >= $time_created_lower";
1112
		}
1113
1114 99
		if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
1115
			$wheres[] = "{$table}.time_updated <= $time_updated_upper";
1116
		}
1117
1118 99
		if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
1119
			$wheres[] = "{$table}.time_updated >= $time_updated_lower";
1120
		}
1121
1122 99 View Code Duplication
		if (is_array($wheres) && count($wheres) > 0) {
1123
			$where_str = implode(' AND ', $wheres);
1124
			return "($where_str)";
1125
		}
1126
1127 99
		return '';
1128
	}
1129
1130
	/**
1131
	 * Gets entities based upon attributes in secondary tables.
1132
	 * Also accepts all options available to elgg_get_entities(),
1133
	 * elgg_get_entities_from_metadata(), and elgg_get_entities_from_relationship().
1134
	 *
1135
	 * @warning requires that the entity type be specified and there can only be one
1136
	 * type.
1137
	 *
1138
	 * @see elgg_get_entities
1139
	 * @see elgg_get_entities_from_metadata
1140
	 * @see elgg_get_entities_from_relationship
1141
	 *
1142
	 * @param array $options Array in format:
1143
	 *
1144
	 * 	attribute_name_value_pairs => ARR (
1145
	 *                                   'name' => 'name',
1146
	 *                                   'value' => 'value',
1147
	 *                                   'operand' => '=', (optional)
1148
	 *                                   'case_sensitive' => false (optional)
1149
	 *                                  )
1150
	 * 	                             If multiple values are sent via
1151
	 *                               an array ('value' => array('value1', 'value2')
1152
	 *                               the pair's operand will be forced to "IN".
1153
	 *
1154
	 * 	attribute_name_value_pairs_operator => null|STR The operator to use for combining
1155
	 *                                        (name = value) OPERATOR (name = value); default is AND
1156
	 *
1157
	 * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors.
1158
	 * @throws InvalidArgumentException
1159
	 * @todo Does not support ordering by attributes or using an attribute pair shortcut like this ('title' => 'foo')
1160
	 */
1161
	public function getEntitiesFromAttributes(array $options = []) {
1162
		$defaults = [
1163
			'attribute_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
1164
			'attribute_name_value_pairs_operator' => 'AND',
1165
		];
1166
1167
		$options = array_merge($defaults, $options);
1168
1169
		$singulars = ['type', 'attribute_name_value_pair'];
1170
		$options = _elgg_normalize_plural_options_array($options, $singulars);
1171
1172
		$clauses = _elgg_get_entity_attribute_where_sql($options);
1173
1174 View Code Duplication
		if ($clauses) {
1175
			// merge wheres to pass to elgg_get_entities()
1176
			if (isset($options['wheres']) && !is_array($options['wheres'])) {
1177
				$options['wheres'] = [$options['wheres']];
1178
			} elseif (!isset($options['wheres'])) {
1179
				$options['wheres'] = [];
1180
			}
1181
1182
			$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
1183
1184
			// merge joins to pass to elgg_get_entities()
1185
			if (isset($options['joins']) && !is_array($options['joins'])) {
1186
				$options['joins'] = [$options['joins']];
1187
			} elseif (!isset($options['joins'])) {
1188
				$options['joins'] = [];
1189
			}
1190
1191
			$options['joins'] = array_merge($options['joins'], $clauses['joins']);
1192
		}
1193
1194
		return elgg_get_entities_from_relationship($options);
1195
	}
1196
1197
	/**
1198
	 * Get the join and where clauses for working with entity attributes
1199
	 *
1200
	 * @return false|array False on fail, array('joins', 'wheres')
1201
	 * @access private
1202
	 * @throws InvalidArgumentException
1203
	 */
1204
	public function getEntityAttributeWhereSql(array $options = []) {
1205
1206
		if (!isset($options['types'])) {
1207
			throw new InvalidArgumentException("The entity type must be defined for elgg_get_entities_from_attributes()");
1208
		}
1209
1210
		if (is_array($options['types']) && count($options['types']) !== 1) {
1211
			throw new InvalidArgumentException("Only one type can be passed to elgg_get_entities_from_attributes()");
1212
		}
1213
1214
		// type can be passed as string or array
1215
		$type = $options['types'];
1216
		if (is_array($type)) {
1217
			$type = $type[0];
1218
		}
1219
1220
		// @todo the types should be defined somewhere (as constant on \ElggEntity?)
1221
		if (!in_array($type, ['group', 'object', 'site', 'user'])) {
1222
			throw new InvalidArgumentException("Invalid type '$type' passed to elgg_get_entities_from_attributes()");
1223
		}
1224
1225
1226
		$type_table = "{$this->db->prefix}{$type}s_entity";
1227
1228
		$return = [
1229
			'joins' => [],
1230
			'wheres' => [],
1231
		];
1232
1233
		// short circuit if nothing requested
1234
		if ($options['attribute_name_value_pairs'] == ELGG_ENTITIES_ANY_VALUE) {
1235
			return $return;
1236
		}
1237
1238
		if (!is_array($options['attribute_name_value_pairs'])) {
1239
			throw new InvalidArgumentException("attribute_name_value_pairs must be an array for elgg_get_entities_from_attributes()");
1240
		}
1241
1242
		$wheres = [];
1243
1244
		// check if this is an array of pairs or just a single pair.
1245
		$pairs = $options['attribute_name_value_pairs'];
1246
		if (isset($pairs['name']) || isset($pairs['value'])) {
1247
			$pairs = [$pairs];
1248
		}
1249
1250
		$pair_wheres = [];
1251
		foreach ($pairs as $index => $pair) {
1252
			// must have at least a name and value
1253
			if (!isset($pair['name']) || !isset($pair['value'])) {
1254
				continue;
1255
			}
1256
1257
			if (isset($pair['operand'])) {
1258
				$operand = sanitize_string($pair['operand']);
1259
			} else {
1260
				$operand = '=';
1261
			}
1262
1263
			if (is_numeric($pair['value'])) {
1264
				$value = sanitize_string($pair['value']);
1265
			} else if (is_array($pair['value'])) {
1266
				$values_array = [];
1267
				foreach ($pair['value'] as $pair_value) {
1268
					if (is_numeric($pair_value)) {
1269
						$values_array[] = sanitize_string($pair_value);
1270
					} else {
1271
						$values_array[] = "'" . sanitize_string($pair_value) . "'";
1272
					}
1273
				}
1274
1275
				$operand = 'IN';
1276
				if ($values_array) {
1277
					$value = '(' . implode(', ', $values_array) . ')';
1278
				}
1279
			} else {
1280
				$value = "'" . sanitize_string($pair['value']) . "'";
1281
			}
1282
1283
			$name = sanitize_string($pair['name']);
1284
1285
			// case sensitivity can be specified per pair
1286
			$pair_binary = '';
1287
			if (isset($pair['case_sensitive'])) {
1288
				$pair_binary = ($pair['case_sensitive']) ? 'BINARY ' : '';
1289
			}
1290
1291
			$pair_wheres[] = "({$pair_binary}type_table.$name $operand $value)";
1292
		}
1293
1294
		if ($where = implode(" {$options['attribute_name_value_pairs_operator']} ", $pair_wheres)) {
1295
			$return['wheres'][] = "($where)";
1296
1297
			$return['joins'][] = "JOIN $type_table type_table ON e.guid = type_table.guid";
1298
		}
1299
1300
		return $return;
1301
	}
1302
1303
	/**
1304
	 * Returns a list of months in which entities were updated or created.
1305
	 *
1306
	 * @tip Use this to generate a list of archives by month for when entities were added or updated.
1307
	 *
1308
	 * @todo document how to pass in array for $subtype
1309
	 *
1310
	 * @warning Months are returned in the form YYYYMM.
1311
	 *
1312
	 * @param string $type           The type of entity
1313
	 * @param string $subtype        The subtype of entity
1314
	 * @param int    $container_guid The container GUID that the entities belong to
1315
	 * @param string $order_by       Order_by SQL order by clause
1316
	 *
1317
	 * @return array|false Either an array months as YYYYMM, or false on failure
1318
	 */
1319
	public function getDates($type = '', $subtype = '', $container_guid = 0, $order_by = 'time_created') {
1320
1321
		$where = [];
1322
1323
		if ($type != "") {
1324
			$type = sanitise_string($type);
1325
			$where[] = "type='$type'";
1326
		}
1327
1328
		if (is_array($subtype)) {
1329
			$tempwhere = "";
1330
			if (sizeof($subtype)) {
1331
				foreach ($subtype as $typekey => $subtypearray) {
1332
					foreach ($subtypearray as $subtypeval) {
1333
						$typekey = sanitise_string($typekey);
0 ignored issues
show
Deprecated Code introduced by
The function sanitise_string() has been deprecated with message: Use query parameters where possible

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
1334
						if (!empty($subtypeval)) {
1335
							if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
1336
								return false;
1337
							}
1338
						} else {
1339
							$subtypeval = 0;
1340
						}
1341
						if (!empty($tempwhere)) {
1342
							$tempwhere .= " or ";
1343
						}
1344
						$tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
1345
					}
1346
				}
1347
			}
1348
			if (!empty($tempwhere)) {
1349
				$where[] = "({$tempwhere})";
1350
			}
1351
		} else {
1352
			if ($subtype) {
1353
				if (!$subtype_id = get_subtype_id($type, $subtype)) {
1354
					return false;
1355
				} else {
1356
					$where[] = "subtype=$subtype_id";
1357
				}
1358
			}
1359
		}
1360
1361
		if ($container_guid !== 0) {
1362
			if (is_array($container_guid)) {
1363
				foreach ($container_guid as $key => $val) {
1364
					$container_guid[$key] = (int) $val;
1365
				}
1366
				$where[] = "container_guid in (" . implode(",", $container_guid) . ")";
1367
			} else {
1368
				$container_guid = (int) $container_guid;
1369
				$where[] = "container_guid = {$container_guid}";
1370
			}
1371
		}
1372
1373
		$where[] = _elgg_get_access_where_sql(['table_alias' => '']);
1374
1375
		$sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
1376
			FROM {$this->db->prefix}entities where ";
1377
1378
		foreach ($where as $w) {
1379
			$sql .= " $w and ";
1380
		}
1381
1382
		$sql .= "1=1 ORDER BY $order_by";
1383
		if ($result = $this->db->getData($sql)) {
1384
			$endresult = [];
1385
			foreach ($result as $res) {
1386
				$endresult[] = $res->yearmonth;
1387
			}
1388
			return $endresult;
1389
		}
1390
		return false;
1391
	}
1392
1393
	/**
1394
	 * Update the last_action column in the entities table for $guid.
1395
	 *
1396
	 * @warning This is different to time_updated.  Time_updated is automatically set,
1397
	 * while last_action is only set when explicitly called.
1398
	 *
1399
	 * @param ElggEntity $entity Entity annotation|relationship action carried out on
1400
	 * @param int        $posted Timestamp of last action
1401
	 * @return int
1402
	 * @access private
1403
	 */
1404 2
	public function updateLastAction(ElggEntity $entity, $posted = null) {
1405
1406 2
		if (!$posted) {
1407 2
			$posted = $this->getCurrentTime()->getTimestamp();
1408
		}
1409
		
1410
		$query = "
1411 2
			UPDATE {$this->db->prefix}entities
1412
			SET last_action = :last_action
1413
			WHERE guid = :guid
1414
		";
1415
1416
		$params = [
1417 2
			':last_action' => (int) $posted,
1418 2
			':guid' => (int) $entity->guid,
1419
		];
1420
		
1421 2
		$this->db->updateData($query, true, $params);
1422
1423 2
		return (int) $posted;
1424
	}
1425
1426
	/**
1427
	 * Get a user by GUID even if the entity is hidden or disabled
1428
	 *
1429
	 * @param int $guid User GUID. Default is logged in user
1430
	 *
1431
	 * @return ElggUser|false
1432
	 * @throws UserFetchFailureException
1433
	 * @access private
1434
	 */
1435 262
	public function getUserForPermissionsCheck($guid = 0) {
1436 262
		if (!$guid) {
1437 95
			return $this->session->getLoggedInUser();
1438
		}
1439
1440
		// need to ignore access and show hidden entities for potential hidden/disabled users
1441 262
		$ia = $this->session->setIgnoreAccess(true);
1442 262
		$show_hidden = access_show_hidden_entities(true);
1443
	
1444 262
		$user = $this->get($guid, 'user');
1445
1446 262
		$this->session->setIgnoreAccess($ia);
1447 262
		access_show_hidden_entities($show_hidden);
1448
1449 262
		if (!$user) {
1450
			// requested to check access for a specific user_guid, but there is no user entity, so the caller
1451
			// should cancel the check and return false
1452 262
			$message = $this->translator->translate('UserFetchFailureException', [$guid]);
1453
			// $this->logger->warn($message);
1454
1455 262
			throw new UserFetchFailureException($message);
1456
		}
1457
1458 170
		return $user;
1459
	}
1460
1461
	/**
1462
	 * Disables all entities owned and contained by a user (or another entity)
1463
	 *
1464
	 * @param int $owner_guid The owner GUID
1465
	 * @return bool
1466
	 */
1467
	public function disableEntities($owner_guid) {
1468
		$entity = get_entity($owner_guid);
1469
		if (!$entity || !$entity->canEdit()) {
1470
			return false;
1471
		}
1472
1473
		if (!$this->events->trigger('disable', $entity->type, $entity)) {
1474
			return false;
1475
		}
1476
1477
		$query = "
1478
			UPDATE {$this->table}entities
1479
			SET enabled='no'
1480
			WHERE owner_guid = :owner_guid
1481
			OR container_guid = :owner_guid";
1482
1483
		$params = [
1484
			':owner_guid' => (int) $owner_guid,
1485
		];
1486
1487
		_elgg_invalidate_cache_for_entity($entity->guid);
1488
		_elgg_invalidate_memcache_for_entity($entity->guid);
1489
		
1490
		if ($this->db->updateData($query, true, $params)) {
1491
			return true;
1492
		}
1493
1494
		return false;
1495
	}
1496
1497
}
1498