EntityTable   F
last analyzed

Complexity

Total Complexity 210

Size/Duplication

Total Lines 1231
Duplicated Lines 14.13 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 1.15%

Importance

Changes 0
Metric Value
dl 174
loc 1231
ccs 7
cts 606
cp 0.0115
rs 0.8
c 0
b 0
f 0
wmc 210
lcom 1
cbo 15

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getRow() 0 12 2
D rowToElggStar() 12 66 18
B get() 0 28 9
A exists() 0 13 2
A enable() 0 15 2
F getEntities() 53 212 34
B autoJoinTables() 0 45 11
F fetchFromSql() 53 91 19
F getEntityTypeSubtypeWhereSql() 4 157 38
B getGuidBasedWhereSql() 0 34 9
B getEntityTimeWhereSql() 4 29 11
B getEntitiesFromAttributes() 19 35 8
F getEntityAttributeWhereSql() 0 99 22
F getDates() 29 84 20
A updateLastAction() 0 22 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EntityTable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EntityTable, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Elgg\Database;
3
4
use IncompleteEntityException;
5
6
/**
7
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
8
 *
9
 * @access private
10
 *
11
 * @package    Elgg.Core
12
 * @subpackage Database
13
 * @since      1.10.0
14
 */
15
class EntityTable {
16
	/**
17
	 * Global Elgg configuration
18
	 * 
19
	 * @var \stdClass
20
	 */
21
	private $CONFIG;
22
23
	/**
24
	 * Constructor
25
	 */
26 2
	public function __construct() {
27 2
		global $CONFIG;
28 2
		$this->CONFIG = $CONFIG;
29 2
	}
30
31
	/**
32
	 * Returns a database row from the entities table.
33
	 *
34
	 * @tip Use get_entity() to return the fully loaded entity.
35
	 *
36
	 * @warning This will only return results if a) it exists, b) you have access to it.
37
	 * see {@link _elgg_get_access_where_sql()}.
38
	 *
39
	 * @param int $guid The GUID of the object to extract
40
	 *
41
	 * @return \stdClass|false
42
	 * @see entity_row_to_elggstar()
43
	 * @access private
44
	 */
45
	function getRow($guid) {
46
		
47
	
48
		if (!$guid) {
49
			return false;
50
		}
51
	
52
		$guid = (int) $guid;
53
		$access = _elgg_get_access_where_sql(array('table_alias' => ''));
54
	
55
		return _elgg_services()->db->getDataRow("SELECT * from {$this->CONFIG->dbprefix}entities where guid=$guid and $access");
56
	}
57
	
58
	/**
59
	 * Create an Elgg* object from a given entity row.
60
	 *
61
	 * Handles loading all tables into the correct class.
62
	 *
63
	 * @param \stdClass $row The row of the entry in the entities table.
64
	 *
65
	 * @return \ElggEntity|false
66
	 * @see get_entity_as_row()
67
	 * @see add_subtype()
68
	 * @see get_entity()
69
	 * @access private
70
	 *
71
	 * @throws \ClassException|\InstallationException
72
	 */
73
	function rowToElggStar($row) {
74
		if (!($row instanceof \stdClass)) {
75
			return $row;
76
		}
77
	
78
		if ((!isset($row->guid)) || (!isset($row->subtype))) {
79
			return $row;
80
		}
81
	
82
		$new_entity = false;
83
	
84
		// Create a memcache cache if we can
85
		static $newentity_cache;
86
		if ((!$newentity_cache) && (is_memcache_available())) {
87
			$newentity_cache = new \ElggMemcache('new_entity_cache');
88
		}
89
		if ($newentity_cache) {
90
			$new_entity = $newentity_cache->load($row->guid);
91
		}
92
		if ($new_entity) {
93
			return $new_entity;
94
		}
95
	
96
		// load class for entity if one is registered
97
		$classname = get_subtype_class_from_id($row->subtype);
98 View Code Duplication
		if ($classname != "") {
99
			if (class_exists($classname)) {
100
				$new_entity = new $classname($row);
101
	
102
				if (!($new_entity instanceof \ElggEntity)) {
103
					$msg = $classname . " is not a " . '\ElggEntity' . ".";
104
					throw new \ClassException($msg);
105
				}
106
			} else {
107
				error_log("Class '" . $classname . "' was not found, missing plugin?");
108
			}
109
		}
110
	
111
		if (!$new_entity) {
112
			//@todo Make this into a function
113
			switch ($row->type) {
114
				case 'object' :
115
					$new_entity = new \ElggObject($row);
116
					break;
117
				case 'user' :
118
					$new_entity = new \ElggUser($row);
119
					break;
120
				case 'group' :
121
					$new_entity = new \ElggGroup($row);
122
					break;
123
				case 'site' :
124
					$new_entity = new \ElggSite($row);
125
					break;
126
				default:
127
					$msg = "Entity type " . $row->type . " is not supported.";
128
					throw new \InstallationException($msg);
129
			}
130
		}
131
	
132
		// Cache entity if we have a cache available
133
		if (($newentity_cache) && ($new_entity)) {
134
			$newentity_cache->save($new_entity->guid, $new_entity);
135
		}
136
	
137
		return $new_entity;
138
	}
139
	
140
	/**
141
	 * Loads and returns an entity object from a guid.
142
	 *
143
	 * @param int    $guid The GUID of the entity
144
	 * @param string $type The type of the entity. If given, even an existing entity with the given GUID
145
	 *                     will not be returned unless its type matches.
146
	 *
147
	 * @return \ElggEntity The correct Elgg or custom object based upon entity type and subtype
148
	 */
149 1
	function get($guid, $type = '') {
150
		// We could also use: if (!(int) $guid) { return false },
151
		// but that evaluates to a false positive for $guid = true.
152
		// This is a bit slower, but more thorough.
153 1
		if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
154 1
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Elgg\Database\EntityTable::get of type ElggEntity.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
155
		}
156
		
157
		// Check local cache first
158
		$new_entity = _elgg_retrieve_cached_entity($guid);
0 ignored issues
show
Bug Compatibility introduced by
The expression _elgg_retrieve_cached_entity($guid); of type ElggEntity|boolean adds the type boolean to the return on line 163 which is incompatible with the return type documented by Elgg\Database\EntityTable::get of type ElggEntity.
Loading history...
159
		if ($new_entity) {
160
			if ($type) {
161
				return elgg_instanceof($new_entity, $type) ? $new_entity : false;
0 ignored issues
show
Bug Compatibility introduced by
The expression elgg_instanceof($new_ent... ? $new_entity : false; of type ElggEntity|boolean adds the type boolean to the return on line 161 which is incompatible with the return type documented by Elgg\Database\EntityTable::get of type ElggEntity.
Loading history...
162
			}
163
			return $new_entity;
164
		}
165
166
		$options = [
167
			'guid' => $guid,
168
			'limit' => 1,
169
			'site_guids' => ELGG_ENTITIES_ANY_VALUE, // for BC with get_entity, allow matching any site
170
		];
171
		if ($type) {
172
			$options['type'] = $type;
173
		}
174
		$entities = $this->getEntities($options);
175
		return $entities ? $entities[0] : false;
176
	}
177
	
178
	/**
179
	 * Does an entity exist?
180
	 *
181
	 * This function checks for the existence of an entity independent of access
182
	 * permissions. It is useful for situations when a user cannot access an entity
183
	 * and it must be determined whether entity has been deleted or the access level
184
	 * has changed.
185
	 *
186
	 * @param int $guid The GUID of the entity
187
	 *
188
	 * @return bool
189
	 */
190
	function exists($guid) {
191
		
192
	
193
		$guid = sanitize_int($guid);
194
	
195
		$query = "SELECT count(*) as total FROM {$this->CONFIG->dbprefix}entities WHERE guid = $guid";
196
		$result = _elgg_services()->db->getDataRow($query);
197
		if ($result->total == 0) {
198
			return false;
199
		} else {
200
			return true;
201
		}
202
	}
203
	
204
	/**
205
	 * Enable an entity.
206
	 *
207
	 * @param int  $guid      GUID of entity to enable
208
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
209
	 *
210
	 * @return bool
211
	 */
212
	function enable($guid, $recursive = true) {
213
	
214
		// Override access only visible entities
215
		$old_access_status = access_get_show_hidden_status();
216
		access_show_hidden_entities(true);
217
	
218
		$result = false;
219
		$entity = get_entity($guid);
220
		if ($entity) {
221
			$result = $entity->enable($recursive);
222
		}
223
	
224
		access_show_hidden_entities($old_access_status);
225
		return $result;
226
	}
227
	
228
	/**
229
	 * Returns an array of entities with optional filtering.
230
	 *
231
	 * Entities are the basic unit of storage in Elgg.  This function
232
	 * provides the simplest way to get an array of entities.  There
233
	 * are many options available that can be passed to filter
234
	 * what sorts of entities are returned.
235
	 *
236
	 * @tip To output formatted strings of entities, use {@link elgg_list_entities()} and
237
	 * its cousins.
238
	 *
239
	 * @tip Plural arguments can be written as singular if only specifying a
240
	 * single element.  ('type' => 'object' vs 'types' => array('object')).
241
	 *
242
	 * @param array $options Array in format:
243
	 *
244
	 * 	types => null|STR entity type (type IN ('type1', 'type2')
245
	 *           Joined with subtypes by AND. See below)
246
	 *
247
	 * 	subtypes => null|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2))
248
	 *              Use ELGG_ENTITIES_NO_VALUE to match the default subtype.
249
	 *              Use ELGG_ENTITIES_ANY_VALUE to match any subtype.
250
	 *
251
	 * 	type_subtype_pairs => null|ARR (array('type' => 'subtype'))
252
	 *                        array(
253
	 *                            'object' => array('blog', 'file'), // All objects with subtype of 'blog' or 'file'
254
	 *                            'user' => ELGG_ENTITY_ANY_VALUE, // All users irrespective of subtype
255
	 *                        );
256
	 *
257
	 *	guids => null|ARR Array of entity guids
258
	 *
259
	 * 	owner_guids => null|ARR Array of owner guids
260
	 *
261
	 * 	container_guids => null|ARR Array of container_guids
262
	 *
263
	 * 	site_guids => null (current_site)|ARR Array of site_guid
264
	 *
265
	 * 	order_by => null (time_created desc)|STR SQL order by clause
266
	 *
267
	 *  reverse_order_by => BOOL Reverse the default order by clause
268
	 *
269
	 * 	limit => null (10)|INT SQL limit clause (0 means no limit)
270
	 *
271
	 * 	offset => null (0)|INT SQL offset clause
272
	 *
273
	 * 	created_time_lower => null|INT Created time lower boundary in epoch time
274
	 *
275
	 * 	created_time_upper => null|INT Created time upper boundary in epoch time
276
	 *
277
	 * 	modified_time_lower => null|INT Modified time lower boundary in epoch time
278
	 *
279
	 * 	modified_time_upper => null|INT Modified time upper boundary in epoch time
280
	 *
281
	 * 	count => true|false return a count instead of entities
282
	 *
283
	 * 	wheres => array() Additional where clauses to AND together
284
	 *
285
	 * 	joins => array() Additional joins
286
	 *
287
	 * 	preload_owners => bool (false) If set to true, this function will preload
288
	 * 					  all the owners of the returned entities resulting in better
289
	 * 					  performance if those owners need to be displayed
290
	 *
291
	 *  preload_containers => bool (false) If set to true, this function will preload
292
	 * 					      all the containers of the returned entities resulting in better
293
	 * 					      performance if those containers need to be displayed
294
	 *
295
	 *
296
	 * 	callback => string A callback function to pass each row through
297
	 *
298
	 * 	distinct => bool (true) If set to false, Elgg will drop the DISTINCT clause from
299
	 *				the MySQL query, which will improve performance in some situations.
300
	 *				Avoid setting this option without a full understanding of the underlying
301
	 *				SQL query Elgg creates.
302
	 *
303
	 * @return mixed If count, int. If not count, array. false on errors.
304
	 * @see elgg_get_entities_from_metadata()
305
	 * @see elgg_get_entities_from_relationship()
306
	 * @see elgg_get_entities_from_access_id()
307
	 * @see elgg_get_entities_from_annotations()
308
	 * @see elgg_list_entities()
309
	 */
310
	function getEntities(array $options = array()) {
311
		
312
	
313
		$defaults = array(
314
			'types'					=>	ELGG_ENTITIES_ANY_VALUE,
315
			'subtypes'				=>	ELGG_ENTITIES_ANY_VALUE,
316
			'type_subtype_pairs'	=>	ELGG_ENTITIES_ANY_VALUE,
317
	
318
			'guids'					=>	ELGG_ENTITIES_ANY_VALUE,
319
			'owner_guids'			=>	ELGG_ENTITIES_ANY_VALUE,
320
			'container_guids'		=>	ELGG_ENTITIES_ANY_VALUE,
321
			'site_guids'			=>	$this->CONFIG->site_guid,
322
	
323
			'modified_time_lower'	=>	ELGG_ENTITIES_ANY_VALUE,
324
			'modified_time_upper'	=>	ELGG_ENTITIES_ANY_VALUE,
325
			'created_time_lower'	=>	ELGG_ENTITIES_ANY_VALUE,
326
			'created_time_upper'	=>	ELGG_ENTITIES_ANY_VALUE,
327
	
328
			'reverse_order_by'		=>	false,
329
			'order_by' 				=>	'e.time_created desc',
330
			'group_by'				=>	ELGG_ENTITIES_ANY_VALUE,
331
			'limit'					=>	_elgg_services()->config->get('default_limit'),
332
			'offset'				=>	0,
333
			'count'					=>	false,
334
			'selects'				=>	array(),
335
			'wheres'				=>	array(),
336
			'joins'					=>	array(),
337
	
338
			'preload_owners'		=> false,
339
			'preload_containers'	=> false,
340
			'callback'				=> 'entity_row_to_elggstar',
341
			'distinct'				=> true,
342
	
343
			// private API
344
			'__ElggBatch'			=> null,
345
		);
346
	
347
		$options = array_merge($defaults, $options);
348
	
349
		// can't use helper function with type_subtype_pair because
350
		// it's already an array...just need to merge it
351 View Code Duplication
		if (isset($options['type_subtype_pair'])) {
352
			if (isset($options['type_subtype_pairs'])) {
353
				$options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
354
					$options['type_subtype_pair']);
355
			} else {
356
				$options['type_subtype_pairs'] = $options['type_subtype_pair'];
357
			}
358
		}
359
	
360
		$singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid');
361
		$options = _elgg_normalize_plural_options_array($options, $singulars);
362
363
		$options = $this->autoJoinTables($options);
364
365
		// evaluate where clauses
366
		if (!is_array($options['wheres'])) {
367
			$options['wheres'] = array($options['wheres']);
368
		}
369
	
370
		$wheres = $options['wheres'];
371
	
372
		$wheres[] = _elgg_get_entity_type_subtype_where_sql('e', $options['types'],
373
			$options['subtypes'], $options['type_subtype_pairs']);
374
	
375
		$wheres[] = _elgg_get_guid_based_where_sql('e.guid', $options['guids']);
376
		$wheres[] = _elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']);
377
		$wheres[] = _elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']);
378
		$wheres[] = _elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']);
379
	
380
		$wheres[] = _elgg_get_entity_time_where_sql('e', $options['created_time_upper'],
381
			$options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
382
	
383
		// see if any functions failed
384
		// remove empty strings on successful functions
385 View Code Duplication
		foreach ($wheres as $i => $where) {
386
			if ($where === false) {
387
				return false;
388
			} elseif (empty($where)) {
389
				unset($wheres[$i]);
390
			}
391
		}
392
	
393
		// remove identical where clauses
394
		$wheres = array_unique($wheres);
395
	
396
		// evaluate join clauses
397
		if (!is_array($options['joins'])) {
398
			$options['joins'] = array($options['joins']);
399
		}
400
	
401
		// remove identical join clauses
402
		$joins = array_unique($options['joins']);
403
	
404 View Code Duplication
		foreach ($joins as $i => $join) {
405
			if ($join === false) {
406
				return false;
407
			} elseif (empty($join)) {
408
				unset($joins[$i]);
409
			}
410
		}
411
	
412
		// evalutate selects
413 View Code Duplication
		if ($options['selects']) {
414
			$selects = '';
415
			foreach ($options['selects'] as $select) {
416
				$selects .= ", $select";
417
			}
418
		} else {
419
			$selects = '';
420
		}
421
	
422 View Code Duplication
		if (!$options['count']) {
423
			$distinct = $options['distinct'] ? "DISTINCT" : "";
424
			$query = "SELECT $distinct e.*{$selects} FROM {$this->CONFIG->dbprefix}entities e ";
425
		} else {
426
			// note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than GUIDs
427
			$count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*";
428
			$query = "SELECT COUNT($count_expr) as total FROM {$this->CONFIG->dbprefix}entities e ";
429
		}
430
	
431
		// add joins
432
		foreach ($joins as $j) {
433
			$query .= " $j ";
434
		}
435
	
436
		// add wheres
437
		$query .= ' WHERE ';
438
	
439
		foreach ($wheres as $w) {
440
			$query .= " $w AND ";
441
		}
442
	
443
		// Add access controls
444
		$query .= _elgg_get_access_where_sql();
445
	
446
		// reverse order by
447
		if ($options['reverse_order_by']) {
448
			$options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']);
0 ignored issues
show
Documentation introduced by
$options['order_by'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
449
		}
450
451
		if ($options['count']) {
452
			$total = _elgg_services()->db->getDataRow($query);
453
			return (int)$total->total;
454
		}
455
456
		if ($options['group_by']) {
457
			$query .= " GROUP BY {$options['group_by']}";
458
		}
459
460
		if ($options['order_by']) {
461
			$query .= " ORDER BY {$options['order_by']}";
462
		}
463
464 View Code Duplication
		if ($options['limit']) {
465
			$limit = sanitise_int($options['limit'], false);
0 ignored issues
show
Documentation introduced by
$options['limit'] is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
466
			$offset = sanitise_int($options['offset'], false);
0 ignored issues
show
Documentation introduced by
$options['offset'] is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
467
			$query .= " LIMIT $offset, $limit";
468
		}
469
470
		if ($options['callback'] === 'entity_row_to_elggstar') {
471
			if ( isset($options['wetcustom:messages']) )
472
				$results = _elgg_wet_fetch_entities_from_sql($query, $options['__ElggBatch']);      // Patched to allow for much faster message inbox loading with all messages being loaded into a data table
473
			else
474
				$results = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']);
475
		} else {
476
			$results = _elgg_services()->db->getData($query, $options['callback']);
0 ignored issues
show
Bug introduced by
It seems like $options['callback'] can also be of type array; however, Elgg\Database::getData() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
477
		}
478
479
		if (!$results) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $results 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 empty(..) or ! empty(...) instead.

Loading history...
480
			// no results, no preloading
481
			return $results;
482
		}
483
484
		// populate entity and metadata caches, and prepare $entities for preloader
485
		$guids = array();
486 View Code Duplication
		foreach ($results as $item) {
487
			// A custom callback could result in items that aren't \ElggEntity's, so check for them
488
			if ($item instanceof \ElggEntity) {
489
				_elgg_cache_entity($item);
490
				// plugins usually have only settings
491
				if (!$item instanceof \ElggPlugin) {
492
					$guids[] = $item->guid;
493
				}
494
			}
495
		}
496
		// @todo Without this, recursive delete fails. See #4568
497
		reset($results);
498
499
		if ($guids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guids 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 empty(..) or ! empty(...) instead.

Loading history...
500
			// there were entities in the result set, preload metadata for them
501
			_elgg_services()->metadataCache->populateFromEntities($guids);
502
		}
503
504
		if (count($results) > 1) {
505
			$props_to_preload = [];
506
			if ($options['preload_owners']) {
507
				$props_to_preload[] = 'owner_guid';
508
			}
509
			if ($options['preload_containers']) {
510
				$props_to_preload[] = 'container_guid';
511
			}
512
			if ($props_to_preload) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $props_to_preload 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 empty(..) or ! empty(...) instead.

Loading history...
513
				// note, ElggEntityPreloaderIntegrationTest assumes it can swap out
514
				// the preloader after boot. If you inject this component at construction
515
				// time that unit test will break. :/
516
				_elgg_services()->entityPreloader->preload($results, $props_to_preload);
517
			}
518
		}
519
520
		return $results;
521
	}
522
523
	/**
524
	 * Decorate getEntities() options in order to auto-join secondary tables where it's
525
	 * safe to do so.
526
	 *
527
	 * @param array $options Options array in getEntities() after normalization
528
	 * @return array
529
	 */
530
	protected function autoJoinTables(array $options) {
531
		// we must be careful that the query doesn't specify any options that may join
532
		// tables or change the selected columns
533
		if (!is_array($options['types'])
534
				|| count($options['types']) !== 1
535
				|| !empty($options['selects'])
536
				|| !empty($options['wheres'])
537
				|| !empty($options['joins'])
538
				|| $options['callback'] !== 'entity_row_to_elggstar'
539
				|| $options['count']) {
540
			// Too dangerous to auto-join
541
			return $options;
542
		}
543
544
		$join_types = [
545
			// Each class must have a static getExternalAttributes() : array
546
			'object' => 'ElggObject',
547
			'user' => 'ElggUser',
548
			'group' => 'ElggGroup',
549
			'site' => 'ElggSite',
550
		];
551
552
		// We use reset() because $options['types'] may not have a numeric key
553
		$type = reset($options['types']);
554
		if (empty($join_types[$type])) {
555
			return $options;
556
		}
557
558
		// Get the columns we'll need to select. We can't use st.* because the order_by
559
		// clause may reference "guid", which MySQL will complain about being ambiguous
560
		if (!is_callable([$join_types[$type], 'getExternalAttributes'])) {
561
			// for some reason can't get external attributes.
562
			return $options;
563
		}
564
565
		$attributes = $join_types[$type]::getExternalAttributes();
0 ignored issues
show
Bug introduced by
The method getExternalAttributes cannot be called on $join_types[$type] (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
566
		foreach (array_keys($attributes) as $col) {
567
			$options['selects'][] = "st.$col";
568
		}
569
570
		// join the secondary table
571
		$options['joins'][] = "JOIN {$this->CONFIG->dbprefix}{$type}s_entity st ON (e.guid = st.guid)";
572
573
		return $options;
574
	}
575
	
576
	/**
577
	 * Return entities from an SQL query generated by elgg_get_entities.
578
	 *
579
	 * @param string    $sql
580
	 * @param \ElggBatch $batch
581
	 * @return \ElggEntity[]
582
	 *
583
	 * @access private
584
	 * @throws \LogicException
585
	 */
586
	function fetchFromSql($sql, \ElggBatch $batch = null) {
587
		static $plugin_subtype;
588
		if (null === $plugin_subtype) {
589
			$plugin_subtype = get_subtype_id('object', 'plugin');
590
		}
591
	
592
		// Keys are types, values are columns that, if present, suggest that the secondary
593
		// table is already JOINed. Note it's OK if guess incorrectly because entity load()
594
		// will fetch any missing attributes.
595
		$types_to_optimize = array(
596
			'object' => 'title',
597
			'user' => 'password',
598
			'group' => 'name',
599
			'site' => 'url',
600
		);
601
	
602
		$rows = _elgg_services()->db->getData($sql);
603
	
604
		// guids to look up in each type
605
		$lookup_types = array();
606
		// maps GUIDs to the $rows key
607
		$guid_to_key = array();
608
	
609 View Code Duplication
		if (isset($rows[0]->type, $rows[0]->subtype)
610
				&& $rows[0]->type === 'object'
611
				&& $rows[0]->subtype == $plugin_subtype) {
612
			// Likely the entire resultset is plugins, which have already been optimized
613
			// to JOIN the secondary table. In this case we allow retrieving from cache,
614
			// but abandon the extra queries.
615
			$types_to_optimize = array();
616
		}
617
	
618
		// First pass: use cache where possible, gather GUIDs that we're optimizing
619 View Code Duplication
		foreach ($rows as $i => $row) {
620
			if (empty($row->guid) || empty($row->type)) {
621
				throw new \LogicException('Entity row missing guid or type');
622
			}
623
			$entity = _elgg_retrieve_cached_entity($row->guid);
624
			if ($entity) {
625
				$entity->refresh($row);
626
				$rows[$i] = $entity;
627
				continue;
628
			}
629
			if (isset($types_to_optimize[$row->type])) {
630
				// check if row already looks JOINed.
631
				if (isset($row->{$types_to_optimize[$row->type]})) {
632
					// Row probably already contains JOINed secondary table. Don't make another query just
633
					// to pull data that's already there
634
					continue;
635
				}
636
				$lookup_types[$row->type][] = $row->guid;
637
				$guid_to_key[$row->guid] = $i;
638
			}
639
		}
640
		// Do secondary queries and merge rows
641
		if ($lookup_types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lookup_types 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 empty(..) or ! empty(...) instead.

Loading history...
642
			$dbprefix = _elgg_services()->config->get('dbprefix');
643
	
644
			foreach ($lookup_types as $type => $guids) {
645
				$set = "(" . implode(',', $guids) . ")";
646
				$sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set";
647
				$secondary_rows = _elgg_services()->db->getData($sql);
648 View Code Duplication
				if ($secondary_rows) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
649
					foreach ($secondary_rows as $secondary_row) {
650
						$key = $guid_to_key[$secondary_row->guid];
651
						// cast to arrays to merge then cast back
652
						$rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row);
653
					}
654
				}
655
			}
656
		}
657
		// Second pass to finish conversion
658 View Code Duplication
		foreach ($rows as $i => $row) {
659
			if ($row instanceof \ElggEntity) {
660
				continue;
661
			} else {
662
				try {
663
					$rows[$i] = entity_row_to_elggstar($row);
664
				} catch (IncompleteEntityException $e) {
665
					// don't let incomplete entities throw fatal errors
666
					unset($rows[$i]);
667
	
668
					// report incompletes to the batch process that spawned this query
669
					if ($batch) {
670
						$batch->reportIncompleteEntity($row);
671
					}
672
				}
673
			}
674
		}
675
		return $rows;
676
	}
677
	
678
	/**
679
	 * Returns SQL where clause for type and subtype on main entity table
680
	 *
681
	 * @param string     $table    Entity table prefix as defined in SELECT...FROM entities $table
682
	 * @param null|array $types    Array of types or null if none.
683
	 * @param null|array $subtypes Array of subtypes or null if none
684
	 * @param null|array $pairs    Array of pairs of types and subtypes
685
	 *
686
	 * @return false|string
687
	 * @access private
688
	 */
689
	function getEntityTypeSubtypeWhereSql($table, $types, $subtypes, $pairs) {
690
		// subtype depends upon type.
691
		if ($subtypes && !$types) {
692
			_elgg_services()->logger->warn("Cannot set subtypes without type.");
693
			return false;
694
		}
695
	
696
		// short circuit if nothing is requested
697
		if (!$types && !$subtypes && !$pairs) {
698
			return '';
699
		}
700
	
701
		// these are the only valid types for entities in elgg
702
		$valid_types = _elgg_services()->config->get('entity_types');
703
	
704
		// pairs override
705
		$wheres = array();
706
		if (!is_array($pairs)) {
707
			if (!is_array($types)) {
708
				$types = array($types);
709
			}
710
	
711
			if ($subtypes && !is_array($subtypes)) {
712
				$subtypes = array($subtypes);
713
			}
714
	
715
			// decrementer for valid types.  Return false if no valid types
716
			$valid_types_count = count($types);
717
			$valid_subtypes_count = 0;
718
			// remove invalid types to get an accurate count of
719
			// valid types for the invalid subtype detection to use
720
			// below.
721
			// also grab the count of ALL subtypes on valid types to decrement later on
722
			// and check against.
723
			//
724
			// yes this is duplicating a foreach on $types.
725
			foreach ($types as $type) {
726
				if (!in_array($type, $valid_types)) {
727
					$valid_types_count--;
728
					unset($types[array_search($type, $types)]);
729
				} else {
730
					// do the checking (and decrementing) in the subtype section.
731
					$valid_subtypes_count += count($subtypes);
732
				}
733
			}
734
	
735
			// return false if nothing is valid.
736
			if (!$valid_types_count) {
737
				return false;
738
			}
739
	
740
			// subtypes are based upon types, so we need to look at each
741
			// type individually to get the right subtype id.
742
			foreach ($types as $type) {
743
				$subtype_ids = array();
744
				if ($subtypes) {
745
					foreach ($subtypes as $subtype) {
746
						// check that the subtype is valid
747
						if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
748
							// subtype value is 0
749
							$subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
750
						} elseif (!$subtype) {
751
							// subtype is ignored.
752
							// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
753
							continue;
754
						} else {
755
							$subtype_id = get_subtype_id($type, $subtype);
756
							
757
							if ($subtype_id) {
758
								$subtype_ids[] = $subtype_id;
759
							} else {
760
								$valid_subtypes_count--;
761
								_elgg_services()->logger->notice("Type-subtype '$type:$subtype' does not exist!");
762
								continue;
763
							}
764
						}
765
					}
766
	
767
					// return false if we're all invalid subtypes in the only valid type
768
					if ($valid_subtypes_count <= 0) {
769
						return false;
770
					}
771
				}
772
	
773
				if (is_array($subtype_ids) && count($subtype_ids)) {
774
					$subtype_ids_str = implode(',', $subtype_ids);
775
					$wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
776
				} else {
777
					$wheres[] = "({$table}.type = '$type')";
778
				}
779
			}
780
		} else {
781
			// using type/subtype pairs
782
			$valid_pairs_count = count($pairs);
783
			$valid_pairs_subtypes_count = 0;
784
	
785
			// same deal as above--we need to know how many valid types
786
			// and subtypes we have before hitting the subtype section.
787
			// also normalize the subtypes into arrays here.
788
			foreach ($pairs as $paired_type => $paired_subtypes) {
789
				if (!in_array($paired_type, $valid_types)) {
790
					$valid_pairs_count--;
791
					unset($pairs[array_search($paired_type, $pairs)]);
792
				} else {
793
					if ($paired_subtypes && !is_array($paired_subtypes)) {
794
						$pairs[$paired_type] = array($paired_subtypes);
795
					}
796
					$valid_pairs_subtypes_count += count($paired_subtypes);
797
				}
798
			}
799
	
800
			if ($valid_pairs_count <= 0) {
801
				return false;
802
			}
803
			foreach ($pairs as $paired_type => $paired_subtypes) {
804
				// this will always be an array because of line 2027, right?
805
				// no...some overly clever person can say pair => array('object' => null)
806
				if (is_array($paired_subtypes)) {
807
					$paired_subtype_ids = array();
808
					foreach ($paired_subtypes as $paired_subtype) {
809
						if (ELGG_ENTITIES_NO_VALUE === $paired_subtype
810
						|| ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
811
	
812
							$paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
813
								ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
0 ignored issues
show
Bug introduced by
The variable $paired_subtype_id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
814
						} else {
815
							$valid_pairs_subtypes_count--;
816
							_elgg_services()->logger->notice("Type-subtype '$paired_type:$paired_subtype' does not exist!");
817
							// return false if we're all invalid subtypes in the only valid type
818
							continue;
819
						}
820
					}
821
	
822
					// return false if there are no valid subtypes.
823
					if ($valid_pairs_subtypes_count <= 0) {
824
						return false;
825
					}
826
	
827
	
828
					if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
829
						$wheres[] = "({$table}.type = '$paired_type'"
830
							. " AND {$table}.subtype IN ($paired_subtype_ids_str))";
831
					}
832
				} else {
833
					$wheres[] = "({$table}.type = '$paired_type')";
834
				}
835
			}
836
		}
837
	
838
		// pairs override the above.  return false if they don't exist.
839 View Code Duplication
		if (is_array($wheres) && count($wheres)) {
840
			$where = implode(' OR ', $wheres);
841
			return "($where)";
842
		}
843
	
844
		return '';
845
	}
846
	
847
	/**
848
	 * Returns SQL where clause for owner and containers.
849
	 *
850
	 * @param string     $column Column name the guids should be checked against. Usually
851
	 *                           best to provide in table.column format.
852
	 * @param null|array $guids  Array of GUIDs.
853
	 *
854
	 * @return false|string
855
	 * @access private
856
	 */
857
	function getGuidBasedWhereSql($column, $guids) {
858
		// short circuit if nothing requested
859
		// 0 is a valid guid
860
		if (!$guids && $guids !== 0) {
861
			return '';
862
		}
863
	
864
		// normalize and sanitise owners
865
		if (!is_array($guids)) {
866
			$guids = array($guids);
867
		}
868
	
869
		$guids_sanitized = array();
870
		foreach ($guids as $guid) {
871
			if ($guid !== ELGG_ENTITIES_NO_VALUE) {
872
				$guid = sanitise_int($guid);
873
	
874
				if (!$guid) {
875
					return false;
876
				}
877
			}
878
			$guids_sanitized[] = $guid;
879
		}
880
	
881
		$where = '';
882
		$guid_str = implode(',', $guids_sanitized);
883
	
884
		// implode(',', 0) returns 0.
885
		if ($guid_str !== false && $guid_str !== '') {
886
			$where = "($column IN ($guid_str))";
887
		}
888
	
889
		return $where;
890
	}
891
	
892
	/**
893
	 * Returns SQL where clause for entity time limits.
894
	 *
895
	 * @param string   $table              Entity table prefix as defined in
896
	 *                                     SELECT...FROM entities $table
897
	 * @param null|int $time_created_upper Time created upper limit
898
	 * @param null|int $time_created_lower Time created lower limit
899
	 * @param null|int $time_updated_upper Time updated upper limit
900
	 * @param null|int $time_updated_lower Time updated lower limit
901
	 *
902
	 * @return false|string false on fail, string on success.
903
	 * @access private
904
	 */
905
	function getEntityTimeWhereSql($table, $time_created_upper = null,
906
	$time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) {
907
	
908
		$wheres = array();
909
	
910
		// exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
911
		if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $time_created_upper of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
912
			$wheres[] = "{$table}.time_created <= $time_created_upper";
913
		}
914
	
915
		if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $time_created_lower of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
916
			$wheres[] = "{$table}.time_created >= $time_created_lower";
917
		}
918
	
919
		if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $time_updated_upper of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
920
			$wheres[] = "{$table}.time_updated <= $time_updated_upper";
921
		}
922
	
923
		if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $time_updated_lower of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
924
			$wheres[] = "{$table}.time_updated >= $time_updated_lower";
925
		}
926
	
927 View Code Duplication
		if (is_array($wheres) && count($wheres) > 0) {
928
			$where_str = implode(' AND ', $wheres);
929
			return "($where_str)";
930
		}
931
	
932
		return '';
933
	}
934
	
935
	/**
936
	 * Gets entities based upon attributes in secondary tables.
937
	 * Also accepts all options available to elgg_get_entities(),
938
	 * elgg_get_entities_from_metadata(), and elgg_get_entities_from_relationship().
939
	 *
940
	 * @warning requires that the entity type be specified and there can only be one
941
	 * type.
942
	 *
943
	 * @see elgg_get_entities
944
	 * @see elgg_get_entities_from_metadata
945
	 * @see elgg_get_entities_from_relationship
946
	 *
947
	 * @param array $options Array in format:
948
	 *
949
	 * 	attribute_name_value_pairs => ARR (
950
	 *                                   'name' => 'name',
951
	 *                                   'value' => 'value',
952
	 *                                   'operand' => '=', (optional)
953
	 *                                   'case_sensitive' => false (optional)
954
	 *                                  )
955
	 * 	                             If multiple values are sent via
956
	 *                               an array ('value' => array('value1', 'value2')
957
	 *                               the pair's operand will be forced to "IN".
958
	 *
959
	 * 	attribute_name_value_pairs_operator => null|STR The operator to use for combining
960
	 *                                        (name = value) OPERATOR (name = value); default is AND
961
	 *
962
	 * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors.
963
	 * @throws InvalidArgumentException
964
	 * @todo Does not support ordering by attributes or using an attribute pair shortcut like this ('title' => 'foo')
965
	 */
966
	function getEntitiesFromAttributes(array $options = array()) {
967
		$defaults = array(
968
			'attribute_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
969
			'attribute_name_value_pairs_operator' => 'AND',
970
		);
971
	
972
		$options = array_merge($defaults, $options);
973
	
974
		$singulars = array('type', 'attribute_name_value_pair');
975
		$options = _elgg_normalize_plural_options_array($options, $singulars);
976
	
977
		$clauses = _elgg_get_entity_attribute_where_sql($options);
978
	
979 View Code Duplication
		if ($clauses) {
980
			// merge wheres to pass to elgg_get_entities()
981
			if (isset($options['wheres']) && !is_array($options['wheres'])) {
982
				$options['wheres'] = array($options['wheres']);
983
			} elseif (!isset($options['wheres'])) {
984
				$options['wheres'] = array();
985
			}
986
	
987
			$options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
988
	
989
			// merge joins to pass to elgg_get_entities()
990
			if (isset($options['joins']) && !is_array($options['joins'])) {
991
				$options['joins'] = array($options['joins']);
992
			} elseif (!isset($options['joins'])) {
993
				$options['joins'] = array();
994
			}
995
	
996
			$options['joins'] = array_merge($options['joins'], $clauses['joins']);
997
		}
998
	
999
		return elgg_get_entities_from_relationship($options);
1000
	}
1001
	
1002
	/**
1003
	 * Get the join and where clauses for working with entity attributes
1004
	 *
1005
	 * @return false|array False on fail, array('joins', 'wheres')
1006
	 * @access private
1007
	 * @throws InvalidArgumentException
1008
	 */
1009
	function getEntityAttributeWhereSql(array $options = array()) {
1010
	
1011
		if (!isset($options['types'])) {
1012
			throw new \InvalidArgumentException("The entity type must be defined for elgg_get_entities_from_attributes()");
1013
		}
1014
	
1015
		if (is_array($options['types']) && count($options['types']) !== 1) {
1016
			throw new \InvalidArgumentException("Only one type can be passed to elgg_get_entities_from_attributes()");
1017
		}
1018
	
1019
		// type can be passed as string or array
1020
		$type = $options['types'];
1021
		if (is_array($type)) {
1022
			$type = $type[0];
1023
		}
1024
	
1025
		// @todo the types should be defined somewhere (as constant on \ElggEntity?)
1026
		if (!in_array($type, array('group', 'object', 'site', 'user'))) {
1027
			throw new \InvalidArgumentException("Invalid type '$type' passed to elgg_get_entities_from_attributes()");
1028
		}
1029
	
1030
		
1031
		$type_table = "{$this->CONFIG->dbprefix}{$type}s_entity";
1032
	
1033
		$return = array(
1034
			'joins' => array(),
1035
			'wheres' => array(),
1036
		);
1037
	
1038
		// short circuit if nothing requested
1039
		if ($options['attribute_name_value_pairs'] == ELGG_ENTITIES_ANY_VALUE) {
1040
			return $return;
1041
		}
1042
	
1043
		if (!is_array($options['attribute_name_value_pairs'])) {
1044
			throw new \InvalidArgumentException("attribute_name_value_pairs must be an array for elgg_get_entities_from_attributes()");
1045
		}
1046
	
1047
		$wheres = array();
1048
	
1049
		// check if this is an array of pairs or just a single pair.
1050
		$pairs = $options['attribute_name_value_pairs'];
1051
		if (isset($pairs['name']) || isset($pairs['value'])) {
1052
			$pairs = array($pairs);
1053
		}
1054
	
1055
		$pair_wheres = array();
1056
		foreach ($pairs as $index => $pair) {
1057
			// must have at least a name and value
1058
			if (!isset($pair['name']) || !isset($pair['value'])) {
1059
				continue;
1060
			}
1061
	
1062
			if (isset($pair['operand'])) {
1063
				$operand = sanitize_string($pair['operand']);
1064
			} else {
1065
				$operand = '=';
1066
			}
1067
	
1068
			if (is_numeric($pair['value'])) {
1069
				$value = sanitize_string($pair['value']);
1070
			} else if (is_array($pair['value'])) {
1071
				$values_array = array();
1072
				foreach ($pair['value'] as $pair_value) {
1073
					if (is_numeric($pair_value)) {
1074
						$values_array[] = sanitize_string($pair_value);
1075
					} else {
1076
						$values_array[] = "'" . sanitize_string($pair_value) . "'";
1077
					}
1078
				}
1079
	
1080
				$operand = 'IN';
1081
				if ($values_array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values_array 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 empty(..) or ! empty(...) instead.

Loading history...
1082
					$value = '(' . implode(', ', $values_array) . ')';
1083
				}
1084
	
1085
			} else {
1086
				$value = "'" . sanitize_string($pair['value']) . "'";
1087
			}
1088
	
1089
			$name = sanitize_string($pair['name']);
1090
	
1091
			// case sensitivity can be specified per pair
1092
			$pair_binary = '';
1093
			if (isset($pair['case_sensitive'])) {
1094
				$pair_binary = ($pair['case_sensitive']) ? 'BINARY ' : '';
1095
			}
1096
	
1097
			$pair_wheres[] = "({$pair_binary}type_table.$name $operand $value)";
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1098
		}
1099
	
1100
		if ($where = implode(" {$options['attribute_name_value_pairs_operator']} ", $pair_wheres)) {
1101
			$return['wheres'][] = "($where)";
1102
	
1103
			$return['joins'][] = "JOIN $type_table type_table ON e.guid = type_table.guid";
1104
		}
1105
	
1106
		return $return;
1107
	}
1108
	
1109
	/**
1110
	 * Returns a list of months in which entities were updated or created.
1111
	 *
1112
	 * @tip Use this to generate a list of archives by month for when entities were added or updated.
1113
	 *
1114
	 * @todo document how to pass in array for $subtype
1115
	 *
1116
	 * @warning Months are returned in the form YYYYMM.
1117
	 *
1118
	 * @param string $type           The type of entity
1119
	 * @param string $subtype        The subtype of entity
1120
	 * @param int    $container_guid The container GUID that the entities belong to
1121
	 * @param int    $site_guid      The site GUID
1122
	 * @param string $order_by       Order_by SQL order by clause
1123
	 *
1124
	 * @return array|false Either an array months as YYYYMM, or false on failure
1125
	 */
1126
	function getDates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0,
1127
			$order_by = 'time_created') {
1128
	
1129
		
1130
	
1131
		$site_guid = (int) $site_guid;
1132
		if ($site_guid == 0) {
1133
			$site_guid = $this->CONFIG->site_guid;
1134
		}
1135
		$where = array();
1136
	
1137
		if ($type != "") {
1138
			$type = sanitise_string($type);
1139
			$where[] = "type='$type'";
1140
		}
1141
	
1142
		if (is_array($subtype)) {
1143
			$tempwhere = "";
1144 View Code Duplication
			if (sizeof($subtype)) {
1145
				foreach ($subtype as $typekey => $subtypearray) {
1146
					foreach ($subtypearray as $subtypeval) {
1147
						$typekey = sanitise_string($typekey);
1148
						if (!empty($subtypeval)) {
1149
							if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
1150
								return false;
1151
							}
1152
						} else {
1153
							$subtypeval = 0;
1154
						}
1155
						if (!empty($tempwhere)) {
1156
							$tempwhere .= " or ";
1157
						}
1158
						$tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
1159
					}
1160
				}
1161
			}
1162
			if (!empty($tempwhere)) {
1163
				$where[] = "({$tempwhere})";
1164
			}
1165
		} else {
1166
			if ($subtype) {
1167
				if (!$subtype_id = get_subtype_id($type, $subtype)) {
1168
					return false;
1169
				} else {
1170
					$where[] = "subtype=$subtype_id";
1171
				}
1172
			}
1173
		}
1174
	
1175 View Code Duplication
		if ($container_guid !== 0) {
1176
			if (is_array($container_guid)) {
1177
				foreach ($container_guid as $key => $val) {
1178
					$container_guid[$key] = (int) $val;
1179
				}
1180
				$where[] = "container_guid in (" . implode(",", $container_guid) . ")";
1181
			} else {
1182
				$container_guid = (int) $container_guid;
1183
				$where[] = "container_guid = {$container_guid}";
1184
			}
1185
		}
1186
	
1187
		if ($site_guid > 0) {
1188
			$where[] = "site_guid = {$site_guid}";
1189
		}
1190
	
1191
		$where[] = _elgg_get_access_where_sql(array('table_alias' => ''));
1192
	
1193
		$sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
1194
			FROM {$this->CONFIG->dbprefix}entities where ";
1195
	
1196
		foreach ($where as $w) {
1197
			$sql .= " $w and ";
1198
		}
1199
	
1200
		$sql .= "1=1 ORDER BY $order_by";
1201
		if ($result = _elgg_services()->db->getData($sql)) {
1202
			$endresult = array();
1203
			foreach ($result as $res) {
1204
				$endresult[] = $res->yearmonth;
1205
			}
1206
			return $endresult;
1207
		}
1208
		return false;
1209
	}
1210
	
1211
	/**
1212
	 * Update the last_action column in the entities table for $guid.
1213
	 *
1214
	 * @warning This is different to time_updated.  Time_updated is automatically set,
1215
	 * while last_action is only set when explicitly called.
1216
	 *
1217
	 * @param int $guid   Entity annotation|relationship action carried out on
1218
	 * @param int $posted Timestamp of last action
1219
	 *
1220
	 * @return bool
1221
	 * @access private
1222
	 */
1223
	function updateLastAction($guid, $posted = null) {
1224
		
1225
		$guid = (int)$guid;
1226
		$posted = (int)$posted;
1227
	
1228
		if (!$posted) {
1229
			$posted = time();
1230
		}
1231
	
1232
		if ($guid) {
1233
			//now add to the river updated table
1234
			$query = "UPDATE {$this->CONFIG->dbprefix}entities SET last_action = {$posted} WHERE guid = {$guid}";
1235
			$result = _elgg_services()->db->updateData($query);
1236
			if ($result) {
1237
				return true;
1238
			} else {
1239
				return false;
1240
			}
1241
		} else {
1242
			return false;
1243
		}
1244
	}
1245
}
1246