Passed
Push — master ( 5063d9...a1994b )
by Jeroen
22:08
created

EntityTable::fetchFromSql()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.2017

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 5
nop 2
dl 0
loc 24
ccs 7
cts 11
cp 0.6364
crap 6.2017
rs 8.5125
c 0
b 0
f 0
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 LogicException;
27
use stdClass;
28
29
/**
30
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
31
 *
32
 * @access private
33
 *
34
 * @package    Elgg.Core
35
 * @subpackage Database
36
 * @since      1.10.0
37
 */
38
class EntityTable {
39
40
	use \Elgg\TimeUsing;
41
	
42
	/**
43
	 * @var Conf
44
	 */
45
	protected $config;
46
47
	/**
48
	 * @var Database
49
	 */
50
	protected $db;
51
52
	/**
53
	 * @var string
54
	 */
55
	protected $table;
56
57
	/**
58
	 * @var SubtypeTable
59
	 */
60
	protected $subtype_table;
61
62
	/**
63
	 * @var EntityCache
64
	 */
65
	protected $entity_cache;
66
67
	/**
68
	 * @var EntityPreloader
69
	 */
70
	protected $entity_preloader;
71
72
	/**
73
	 * @var MetadataCache
74
	 */
75
	protected $metadata_cache;
76
77
	/**
78
	 * @var EventsService
79
	 */
80
	protected $events;
81
82
	/**
83
	 * @var ElggSession
84
	 */
85
	protected $session;
86
87
	/**
88
	 * @var Translator
89
	 */
90
	protected $translator;
91
92
	/**
93
	 * @var Logger
94
	 */
95
	protected $logger;
96
97
	/**
98
	 * Constructor
99
	 *
100
	 * @param Conf          $config         Config
101
	 * @param Database      $db             Database
102
	 * @param EntityCache   $entity_cache   Entity cache
103
	 * @param MetadataCache $metadata_cache Metadata cache
104
	 * @param SubtypeTable  $subtype_table  Subtype table
105
	 * @param EventsService $events         Events service
106
	 * @param ElggSession   $session        Session
107
	 * @param Translator    $translator     Translator
108
	 * @param Logger        $logger         Logger
109
	 */
110 3711 View Code Duplication
	public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
		Conf $config,
112
		Database $db,
113
		EntityCache $entity_cache,
114
		MetadataCache $metadata_cache,
115
		SubtypeTable $subtype_table,
116
		EventsService $events,
117
		ElggSession $session,
118
		Translator $translator,
119
		Logger $logger
120
	) {
121 3711
		$this->config = $config;
122 3711
		$this->db = $db;
123 3711
		$this->table = $this->db->prefix . 'entities';
124 3711
		$this->entity_cache = $entity_cache;
125 3711
		$this->metadata_cache = $metadata_cache;
126 3711
		$this->subtype_table = $subtype_table;
127 3711
		$this->events = $events;
128 3711
		$this->session = $session;
129 3711
		$this->translator = $translator;
130 3711
		$this->logger = $logger;
131 3711
	}
132
133
	/**
134
	 * Returns a database row from the entities table.
135
	 *
136
	 * @see entity_row_to_elggstar()
137
	 *
138
	 * @tip Use get_entity() to return the fully loaded entity.
139
	 *
140
	 * @warning This will only return results if a) it exists, b) you have access to it.
141
	 * see {@link _elgg_get_access_where_sql()}.
142
	 *
143
	 * @param int $guid      The GUID of the object to extract
144
	 * @param int $user_guid GUID of the user accessing the row
145
	 *                       Defaults to logged in user if null
146
	 *                       Builds an access query for a logged out user if 0
147
	 * @return stdClass|false
148
	 * @access private
149
	 */
150 3711
	public function getRow($guid, $user_guid = null) {
151
152 3711
		if (!$guid) {
153
			return false;
154
		}
155
156 3711
		$access = _elgg_get_access_where_sql([
157 3711
			'table_alias' => '',
158 3711
			'user_guid' => $user_guid,
159
		]);
160
161 3711
		$sql = "SELECT * FROM {$this->db->prefix}entities
162 3711
			WHERE guid = :guid AND $access";
163
164
		$params = [
165 3711
			':guid' => (int) $guid,
166
		];
167
168 3711
		return $this->db->getDataRow($sql, null, $params);
169
	}
170
171
	/**
172
	 * Adds a new row to the entity table
173
	 *
174
	 * @param stdClass $row        Entity base information
175
	 * @param array    $attributes All primary table attributes
176
	 *                             Used by database mock services to allow mocking
177
	 *                             entities that were instantiated using new keyword
178
	 *                             and calling ElggEntity::save()
179
	 * @return int|false
180
	 */
181 207
	public function insertRow(stdClass $row, array $attributes = []) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
182
183 207
		$sql = "INSERT INTO {$this->db->prefix}entities
184
			(type, subtype, owner_guid, container_guid,
185
				access_id, time_created, time_updated, last_action)
186
			VALUES
187
			(:type, :subtype_id, :owner_guid, :container_guid,
188
				:access_id, :time_created, :time_updated, :last_action)";
189
190 207
		return $this->db->insertData($sql, [
191 207
			':type' => $row->type,
192 207
			':subtype_id' => $row->subtype_id,
193 207
			':owner_guid' => $row->owner_guid,
194 207
			':container_guid' => $row->container_guid,
195 207
			':access_id' => $row->access_id,
196 207
			':time_created' => $row->time_created,
197 207
			':time_updated' => $row->time_updated,
198 207
			':last_action' => $row->last_action,
199
		]);
200
	}
201
202
	/**
203
	 * Update entity table row
204
	 *
205
	 * @param int      $guid Entity guid
206
	 * @param stdClass $row  Updated data
207
	 * @return int|false
208
	 */
209 69
	public function updateRow($guid, stdClass $row) {
210
		$sql = "
211 69
			UPDATE {$this->db->prefix}entities
212
			SET owner_guid = :owner_guid,
213
			    access_id = :access_id,
214
				container_guid = :container_guid,
215
				time_created = :time_created,
216
				time_updated = :time_updated
217
			WHERE guid = :guid
218
		";
219
220
		$params = [
221 69
			':owner_guid' => $row->owner_guid,
222 69
			':access_id' => $row->access_id,
223 69
			':container_guid' => $row->container_guid,
224 69
			':time_created' => $row->time_created,
225 69
			':time_updated' => $row->time_updated,
226 69
			':guid' => $guid,
227
		];
228
229 69
		return $this->db->updateData($sql, false, $params);
230
	}
231
232
	/**
233
	 * Create an Elgg* object from a given entity row.
234
	 *
235
	 * Handles loading all tables into the correct class.
236
	 *
237
	 * @see get_entity_as_row()
238
	 * @see add_subtype()
239
	 * @see get_entity()
240
	 *
241
	 * @access private
242
	 *
243
	 * @param stdClass $row The row of the entry in the entities table.
244
	 * @return ElggEntity|false
245
	 * @throws ClassException
246
	 * @throws InstallationException
247
	 */
248 3711
	public function rowToElggStar($row) {
249 3711
		if (!$row instanceof stdClass) {
250
			return $row;
251
		}
252
253 3711
		if (!isset($row->guid) || !isset($row->subtype)) {
254
			return $row;
255
		}
256
	
257 3711
		$class_name = $this->subtype_table->getClassFromId($row->subtype);
258 3711
		if ($class_name && !class_exists($class_name)) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $class_name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
259
			$this->logger->error("Class '$class_name' was not found, missing plugin?");
260
			$class_name = '';
261
		}
262
263 3711
		if (!$class_name) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $class_name of type string|null is loosely compared to false; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
264
			$map = [
265 3711
				'object' => ElggObject::class,
266
				'user' => ElggUser::class,
267
				'group' => ElggGroup::class,
268
				'site' => ElggSite::class,
269
			];
270
271 3711
			if (isset($map[$row->type])) {
272 3711
				$class_name = $map[$row->type];
273
			} else {
274
				throw new InstallationException("Entity type {$row->type} is not supported.");
275
			}
276
		}
277
278 3711
		$entity = new $class_name($row);
279 3711
		if (!$entity instanceof ElggEntity) {
280
			throw new ClassException("$class_name must extend " . ElggEntity::class);
281
		}
282
283 3711
		return $entity;
284
	}
285
286
	/**
287
	 * Get an entity from the in-memory or memcache caches
288
	 *
289
	 * @param int $guid GUID
290
	 *
291
	 * @return \ElggEntity
292
	 */
293 3711
	protected function getFromCache($guid) {
294 3711
		$entity = $this->entity_cache->get($guid);
295 3711
		if ($entity) {
296 3561
			return $entity;
297
		}
298
299 3711
		$memcache = _elgg_get_memcache('new_entity_cache');
300 3711
		$entity = $memcache->load($guid);
301 3711
		if (!$entity instanceof ElggEntity) {
302 3711
			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::getFromCache 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...
303
		}
304
305
		// Validate accessibility if from memcache
306 171
		if (!elgg_get_ignore_access() && !has_access_to_entity($entity)) {
307 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::getFromCache 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...
308
		}
309
310 171
		$this->entity_cache->set($entity);
311 171
		return $entity;
312
	}
313
314
	/**
315
	 * Loads and returns an entity object from a guid.
316
	 *
317
	 * @param int    $guid The GUID of the entity
318
	 * @param string $type The type of the entity. If given, even an existing entity with the given GUID
319
	 *                     will not be returned unless its type matches.
320
	 *
321
	 * @return ElggEntity|stdClass|false The correct Elgg or custom object based upon entity type and subtype
322
	 * @throws ClassException
323
	 * @throws InstallationException
324
	 */
325 3711
	public function get($guid, $type = '') {
326
		// We could also use: if (!(int) $guid) { return false },
327
		// but that evaluates to a false positive for $guid = true.
328
		// This is a bit slower, but more thorough.
329 3711
		if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
330 39
			return false;
331
		}
332
333 3711
		$guid = (int) $guid;
334
335 3711
		$entity = $this->getFromCache($guid);
336 3711
		if ($entity && (!$type || elgg_instanceof($entity, $type))) {
337 3620
			return $entity;
338
		}
339
340 3711
		$row = $this->getRow($guid);
341 3711
		if (!$row) {
342 3432
			return false;
343
		}
344
345 348
		if ($type && $row->type != $type) {
346 1
			return false;
347
		}
348
349 348
		$entity = $this->rowToElggStar($row);
0 ignored issues
show
Documentation introduced by
$row is of type array, but the function expects a object<stdClass>.

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...
350
351 348
		if ($entity instanceof ElggEntity) {
352 348
			$entity->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
353
		}
354
355 348
		return $entity;
356
	}
357
358
	/**
359
	 * Does an entity exist?
360
	 *
361
	 * This function checks for the existence of an entity independent of access
362
	 * permissions. It is useful for situations when a user cannot access an entity
363
	 * and it must be determined whether entity has been deleted or the access level
364
	 * has changed.
365
	 *
366
	 * @param int $guid The GUID of the entity
367
	 * @return bool
368
	 */
369 40
	public function exists($guid) {
370
371
		// need to ignore access and show hidden entities to check existence
372 40
		$ia = $this->session->setIgnoreAccess(true);
373 40
		$show_hidden = access_show_hidden_entities(true);
374
375 40
		$result = $this->getRow($guid);
376
377 40
		$this->session->setIgnoreAccess($ia);
378 40
		access_show_hidden_entities($show_hidden);
379
380 40
		return !empty($result);
381
	}
382
383
	/**
384
	 * Enable an entity.
385
	 *
386
	 * @param int  $guid      GUID of entity to enable
387
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
388
	 * @return bool
389
	 */
390
	public function enable($guid, $recursive = true) {
391
392
		// Override access only visible entities
393
		$old_access_status = access_get_show_hidden_status();
394
		access_show_hidden_entities(true);
395
396
		$result = false;
397
		$entity = get_entity($guid);
398
		if ($entity) {
399
			$result = $entity->enable($recursive);
400
		}
401
402
		access_show_hidden_entities($old_access_status);
403
		return $result;
404
	}
405
406
	/**
407
	 * Returns an array of entities with optional filtering.
408
	 *
409
	 * Entities are the basic unit of storage in Elgg.  This function
410
	 * provides the simplest way to get an array of entities.  There
411
	 * are many options available that can be passed to filter
412
	 * what sorts of entities are returned.
413
	 *
414
	 * @tip To output formatted strings of entities, use {@link elgg_list_entities()} and
415
	 * its cousins.
416
	 *
417
	 * @tip Plural arguments can be written as singular if only specifying a
418
	 * single element.  ('type' => 'object' vs 'types' => array('object')).
419
	 *
420
	 * @see elgg_get_entities_from_metadata()
421
	 * @see elgg_get_entities_from_relationship()
422
	 * @see elgg_get_entities_from_access_id()
423
	 * @see elgg_get_entities_from_annotations()
424
	 * @see elgg_list_entities()
425
	 *
426
	 * @param array $options Array in format:
427
	 *
428
	 * 	types => null|STR entity type (type IN ('type1', 'type2')
429
	 *           Joined with subtypes by AND. See below)
430
	 *
431
	 * 	subtypes => null|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2))
432
	 *              Use ELGG_ENTITIES_NO_VALUE to match the default subtype.
433
	 *              Use ELGG_ENTITIES_ANY_VALUE to match any subtype.
434
	 *
435
	 * 	type_subtype_pairs => null|ARR (array('type' => 'subtype'))
436
	 *                        array(
437
	 *                            'object' => array('blog', 'file'), // All objects with subtype of 'blog' or 'file'
438
	 *                            'user' => ELGG_ENTITY_ANY_VALUE, // All users irrespective of subtype
439
	 *                        );
440
	 *
441
	 * 	guids => null|ARR Array of entity guids
442
	 *
443
	 * 	owner_guids => null|ARR Array of owner guids
444
	 *
445
	 * 	container_guids => null|ARR Array of container_guids
446
	 *
447
	 * 	order_by => null (time_created desc)|STR SQL order by clause
448
	 *
449
	 *  reverse_order_by => BOOL Reverse the default order by clause
450
	 *
451
	 * 	limit => null (10)|INT SQL limit clause (0 means no limit)
452
	 *
453
	 * 	offset => null (0)|INT SQL offset clause
454
	 *
455
	 * 	created_time_lower => null|INT Created time lower boundary in epoch time
456
	 *
457
	 * 	created_time_upper => null|INT Created time upper boundary in epoch time
458
	 *
459
	 * 	modified_time_lower => null|INT Modified time lower boundary in epoch time
460
	 *
461
	 * 	modified_time_upper => null|INT Modified time upper boundary in epoch time
462
	 *
463
	 * 	count => true|false return a count instead of entities
464
	 *
465
	 * 	wheres => array() Additional where clauses to AND together
466
	 *
467
	 * 	joins => array() Additional joins
468
	 *
469
	 * 	preload_owners => bool (false) If set to true, this function will preload
470
	 * 					  all the owners of the returned entities resulting in better
471
	 * 					  performance if those owners need to be displayed
472
	 *
473
	 *  preload_containers => bool (false) If set to true, this function will preload
474
	 * 					      all the containers of the returned entities resulting in better
475
	 * 					      performance if those containers need to be displayed
476
	 *
477
	 *
478
	 * 	callback => string A callback function to pass each row through
479
	 *
480
	 * 	distinct => bool (true) If set to false, Elgg will drop the DISTINCT clause from
481
	 * 				the MySQL query, which will improve performance in some situations.
482
	 * 				Avoid setting this option without a full understanding of the underlying
483
	 * 				SQL query Elgg creates.
484
	 *
485
	 *  batch => bool (false) If set to true, an Elgg\BatchResult object will be returned instead of an array.
486
	 *           Since 2.3
487
	 *
488
	 *  batch_inc_offset => bool (true) If "batch" is used, this tells the batch to increment the offset
489
	 *                      on each fetch. This must be set to false if you delete the batched results.
490
	 *
491
	 *  batch_size => int (25) If "batch" is used, this is the number of entities/rows to pull in before
492
	 *                requesting more.
493
	 *
494
	 * @return \ElggEntity[]|int|mixed If count, int. Otherwise an array or an Elgg\BatchResult. false on errors.
495
	 *
496
	 * @see elgg_get_entities_from_metadata()
497
	 * @see elgg_get_entities_from_relationship()
498
	 * @see elgg_get_entities_from_access_id()
499
	 * @see elgg_get_entities_from_annotations()
500
	 * @see elgg_list_entities()
501
	 */
502 505
	public function getEntities(array $options = []) {
503 505
		_elgg_check_unsupported_site_guid($options);
504
505
		$defaults = [
506 505
			'types'                 => ELGG_ENTITIES_ANY_VALUE,
507 505
			'subtypes'              => ELGG_ENTITIES_ANY_VALUE,
508 505
			'type_subtype_pairs'    => ELGG_ENTITIES_ANY_VALUE,
509
510 505
			'guids'                 => ELGG_ENTITIES_ANY_VALUE,
511 505
			'owner_guids'           => ELGG_ENTITIES_ANY_VALUE,
512 505
			'container_guids'       => ELGG_ENTITIES_ANY_VALUE,
513
514 505
			'modified_time_lower'   => ELGG_ENTITIES_ANY_VALUE,
515 505
			'modified_time_upper'   => ELGG_ENTITIES_ANY_VALUE,
516 505
			'created_time_lower'    => ELGG_ENTITIES_ANY_VALUE,
517 505
			'created_time_upper'    => ELGG_ENTITIES_ANY_VALUE,
518
519
			'reverse_order_by'      => false,
520 505
			'order_by'              => 'e.time_created desc',
521 505
			'group_by'              => ELGG_ENTITIES_ANY_VALUE,
522 505
			'limit'                 => $this->config->default_limit,
523 505
			'offset'                => 0,
524
			'count'                 => false,
525
			'selects'               => [],
526
			'wheres'                => [],
527
			'joins'                 => [],
528
529
			'preload_owners'        => false,
530
			'preload_containers'    => false,
531 505
			'callback'              => 'entity_row_to_elggstar',
532
			'distinct'              => true,
533
534
			'batch'                 => false,
535
			'batch_inc_offset'      => true,
536 505
			'batch_size'            => 25,
537
538
			// private API
539
			'__ElggBatch'           => null,
540
		];
541
542 505
		$options = array_merge($defaults, $options);
543
544 505 View Code Duplication
		if ($options['batch'] && !$options['count']) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
545 2
			$batch_size = $options['batch_size'];
546 2
			$batch_inc_offset = $options['batch_inc_offset'];
547
548
			// clean batch keys from $options.
549 2
			unset($options['batch'], $options['batch_size'], $options['batch_inc_offset']);
550
551 2
			return new \ElggBatch([$this, 'getEntities'], $options, null, $batch_size, $batch_inc_offset);
0 ignored issues
show
Documentation introduced by
array($this, 'getEntities') is of type array<integer,this<Elgg\...yTable>","1":"string"}>, 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...
552
		}
553
	
554
		// can't use helper function with type_subtype_pair because
555
		// it's already an array...just need to merge it
556 505 View Code Duplication
		if (isset($options['type_subtype_pair'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
557
			if (isset($options['type_subtype_pairs'])) {
558
				$options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
559
					$options['type_subtype_pair']);
560
			} else {
561
				$options['type_subtype_pairs'] = $options['type_subtype_pair'];
562
			}
563
		}
564
565 505
		$singulars = ['type', 'subtype', 'guid', 'owner_guid', 'container_guid'];
566 505
		$options = _elgg_normalize_plural_options_array($options, $singulars);
567
568
		// evaluate where clauses
569 505
		if (!is_array($options['wheres'])) {
570
			$options['wheres'] = [$options['wheres']];
571
		}
572
573 505
		$wheres = $options['wheres'];
574
575 505
		$wheres[] = $this->getEntityTypeSubtypeWhereSql('e', $options['types'],
576 505
			$options['subtypes'], $options['type_subtype_pairs']);
577
578 505
		$wheres[] = $this->getGuidBasedWhereSql('e.guid', $options['guids']);
579 505
		$wheres[] = $this->getGuidBasedWhereSql('e.owner_guid', $options['owner_guids']);
580 505
		$wheres[] = $this->getGuidBasedWhereSql('e.container_guid', $options['container_guids']);
581
582 505
		$wheres[] = $this->getEntityTimeWhereSql('e', $options['created_time_upper'],
583 505
			$options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
584
585
		// see if any functions failed
586
		// remove empty strings on successful functions
587 505 View Code Duplication
		foreach ($wheres as $i => $where) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
588 505
			if ($where === false) {
589 15
				return false;
590
			} elseif (empty($where)) {
591 505
				unset($wheres[$i]);
592
			}
593
		}
594
595
		// remove identical where clauses
596 504
		$wheres = array_unique($wheres);
597
598
		// evaluate join clauses
599 504 View Code Duplication
		if (!is_array($options['joins'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
600
			$options['joins'] = [$options['joins']];
601
		}
602
603
		// remove identical join clauses
604 504
		$joins = array_unique($options['joins']);
605
606 504 View Code Duplication
		foreach ($joins as $i => $join) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
607 504
			if ($join === false) {
608
				return false;
609
			} elseif (empty($join)) {
610 504
				unset($joins[$i]);
611
			}
612
		}
613
614
		// evalutate selects
615 504
		if ($options['selects']) {
616 293
			$selects = '';
617 293
			foreach ($options['selects'] as $select) {
618 293
				$selects .= ", $select";
619
			}
620
		} else {
621 504
			$selects = '';
622
		}
623
624 504
		if (!$options['count']) {
625 504
			$distinct = $options['distinct'] ? "DISTINCT" : "";
626 504
			$query = "SELECT $distinct e.*{$selects} FROM {$this->db->prefix}entities e ";
627
		} else {
628
			// note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than GUIDs
629 13
			$count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*";
630 13
			$query = "SELECT COUNT($count_expr) as total FROM {$this->db->prefix}entities e ";
631
		}
632
633
		// add joins
634 504
		foreach ($joins as $j) {
635 504
			$query .= " $j ";
636
		}
637
638
		// add wheres
639 504
		$query .= ' WHERE ';
640
641 504
		foreach ($wheres as $w) {
642 504
			$query .= " $w AND ";
643
		}
644
645
		// Add access controls
646 504
		$query .= _elgg_get_access_where_sql();
647
648
		// reverse order by
649 504
		if ($options['reverse_order_by']) {
650
			$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...
651
		}
652
653 504
		if ($options['count']) {
654 13
			$total = $this->db->getDataRow($query);
655 13
			return (int) $total->total;
656
		}
657
658 504
		if ($options['group_by']) {
659 12
			$query .= " GROUP BY {$options['group_by']}";
660
		}
661
662 504
		if ($options['order_by']) {
663 504
			$query .= " ORDER BY {$options['order_by']}";
664
		}
665
666 504 View Code Duplication
		if ($options['limit']) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
667 504
			$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...
Deprecated Code introduced by
The function sanitise_int() 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...
668 504
			$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...
Deprecated Code introduced by
The function sanitise_int() 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...
669 504
			$query .= " LIMIT $offset, $limit";
670
		}
671
672 504
		if ($options['callback'] === 'entity_row_to_elggstar') {
673 504
			$results = $this->fetchFromSql($query, $options['__ElggBatch']);
674
		} else {
675 5
			$results = $this->db->getData($query, $options['callback']);
676
		}
677
678 504
		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...
679
			// no results, no preloading
680 504
			return $results;
681
		}
682
683
		// populate entity and metadata caches, and prepare $entities for preloader
684 293
		$guids = [];
685 293
		foreach ($results as $item) {
686
			// A custom callback could result in items that aren't \ElggEntity's, so check for them
687 293
			if ($item instanceof ElggEntity) {
688 293
				$this->entity_cache->set($item);
689
				// plugins usually have only settings
690 293
				if (!$item instanceof ElggPlugin) {
691 293
					$guids[] = $item->guid;
692
				}
693
			}
694
		}
695
		// @todo Without this, recursive delete fails. See #4568
696 293
		reset($results);
697
698 293
		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...
699
			// there were entities in the result set, preload metadata for them
700 293
			$this->metadata_cache->populateFromEntities($guids);
701
		}
702
703 293
		if (count($results) > 1) {
704 35
			$props_to_preload = [];
705 35
			if ($options['preload_owners']) {
706 1
				$props_to_preload[] = 'owner_guid';
707
			}
708 35
			if ($options['preload_containers']) {
709
				$props_to_preload[] = 'container_guid';
710
			}
711 35
			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...
712
				// note, ElggEntityPreloaderIntegrationTest assumes it can swap out
713
				// the preloader after boot. If you inject this component at construction
714
				// time that unit test will break. :/
715 1
				_elgg_services()->entityPreloader->preload($results, $props_to_preload);
716
			}
717
		}
718
719 293
		return $results;
720
	}
721
722
	/**
723
	 * Return entities from an SQL query generated by elgg_get_entities.
724
	 *
725
	 * @access private
726
	 *
727
	 * @param string    $sql
728
	 * @param ElggBatch $batch
729
	 * @return ElggEntity[]
730
	 * @throws LogicException
731
	 */
732 504
	public function fetchFromSql($sql, \ElggBatch $batch = null) {
733
		
734 504
		$rows = $this->db->getData($sql);
735
		
736
		// Second pass to finish conversion
737 504
		foreach ($rows as $i => $row) {
738 293
			if ($row instanceof ElggEntity) {
739
				continue;
740
			} else {
741
				try {
742 293
					$rows[$i] = $this->rowToElggStar($row);
743
				} catch (IncompleteEntityException $e) {
744
					// don't let incomplete entities throw fatal errors
745
					unset($rows[$i]);
746
747
					// report incompletes to the batch process that spawned this query
748
					if ($batch) {
749 293
						$batch->reportIncompleteEntity($row);
750
					}
751
				}
752
			}
753
		}
754 504
		return $rows;
755
	}
756
757
	/**
758
	 * Returns SQL where clause for type and subtype on main entity table
759
	 *
760
	 * @param string     $table    Entity table prefix as defined in SELECT...FROM entities $table
761
	 * @param null|array $types    Array of types or null if none.
762
	 * @param null|array $subtypes Array of subtypes or null if none
763
	 * @param null|array $pairs    Array of pairs of types and subtypes
764
	 *
765
	 * @return false|string
766
	 * @access private
767
	 */
768 3705
	public function getEntityTypeSubtypeWhereSql($table, $types, $subtypes, $pairs) {
769
		// subtype depends upon type.
770 3705
		if ($subtypes && !$types) {
771
			$this->logger->warn("Cannot set subtypes without type.");
772
			return false;
773
		}
774
775
		// short circuit if nothing is requested
776 3705
		if (!$types && !$subtypes && !$pairs) {
777 3633
			return '';
778
		}
779
780
		// pairs override
781 505
		$wheres = [];
782 505
		if (!is_array($pairs)) {
783 505
			if (!is_array($types)) {
784 293
				$types = [$types];
785
			}
786
787 505
			if ($subtypes && !is_array($subtypes)) {
788
				$subtypes = [$subtypes];
789
			}
790
791
			// decrementer for valid types.  Return false if no valid types
792 505
			$valid_types_count = count($types);
793 505
			$valid_subtypes_count = 0;
794
			// remove invalid types to get an accurate count of
795
			// valid types for the invalid subtype detection to use
796
			// below.
797
			// also grab the count of ALL subtypes on valid types to decrement later on
798
			// and check against.
799
			//
800
			// yes this is duplicating a foreach on $types.
801 505
			foreach ($types as $type) {
802 505
				if (!in_array($type, \Elgg\Config::getEntityTypes())) {
803 10
					$valid_types_count--;
804 10
					unset($types[array_search($type, $types)]);
805
				} else {
806
					// do the checking (and decrementing) in the subtype section.
807 505
					$valid_subtypes_count += count($subtypes);
808
				}
809
			}
810
811
			// return false if nothing is valid.
812 505
			if (!$valid_types_count) {
813 8
				return false;
814
			}
815
816
			// subtypes are based upon types, so we need to look at each
817
			// type individually to get the right subtype id.
818 505
			foreach ($types as $type) {
819 505
				$subtype_ids = [];
820 505
				if ($subtypes) {
821 294
					foreach ($subtypes as $subtype) {
822
						// check that the subtype is valid
823 294
						if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
824
							// subtype value is 0
825 2
							$subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
826 294
						} elseif (!$subtype) {
827
							// subtype is ignored.
828
							// this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
829 1
							continue;
830
						} else {
831 294
							$subtype_id = get_subtype_id($type, $subtype);
832
833 294
							if ($subtype_id) {
834 293
								$subtype_ids[] = $subtype_id;
835
							} else {
836 3
								$valid_subtypes_count--;
837 3
								$this->logger->notice("Type-subtype '$type:$subtype' does not exist!");
838 294
								continue;
839
							}
840
						}
841
					}
842
843
					// return false if we're all invalid subtypes in the only valid type
844 294
					if ($valid_subtypes_count <= 0) {
845 2
						return false;
846
					}
847
				}
848
849 504
				if (is_array($subtype_ids) && count($subtype_ids)) {
850 293
					$subtype_ids_str = implode(',', $subtype_ids);
851 293
					$wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
852
				} else {
853 504
					$wheres[] = "({$table}.type = '$type')";
854
				}
855
			}
856
		} else {
857
			// using type/subtype pairs
858 8
			$valid_pairs_count = count($pairs);
859 8
			$valid_pairs_subtypes_count = 0;
860
861
			// same deal as above--we need to know how many valid types
862
			// and subtypes we have before hitting the subtype section.
863
			// also normalize the subtypes into arrays here.
864 8
			foreach ($pairs as $paired_type => $paired_subtypes) {
865 8
				if (!in_array($paired_type, \Elgg\Config::getEntityTypes())) {
866 5
					$valid_pairs_count--;
867 5
					unset($pairs[array_search($paired_type, $pairs)]);
868
				} else {
869 3
					if ($paired_subtypes && !is_array($paired_subtypes)) {
870 1
						$pairs[$paired_type] = [$paired_subtypes];
871
					}
872 8
					$valid_pairs_subtypes_count += count($paired_subtypes);
873
				}
874
			}
875
876 8
			if ($valid_pairs_count <= 0) {
877 5
				return false;
878
			}
879 3
			foreach ($pairs as $paired_type => $paired_subtypes) {
880
				// this will always be an array because of line 2027, right?
881
				// no...some overly clever person can say pair => array('object' => null)
882 3
				if (is_array($paired_subtypes)) {
883 3
					$paired_subtype_ids = [];
884 3
					foreach ($paired_subtypes as $paired_subtype) {
885 3
						if (ELGG_ENTITIES_NO_VALUE === $paired_subtype || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
886 3
							$paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
887 3
									ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
1 ignored issue
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...
888
						} else {
889 1
							$valid_pairs_subtypes_count--;
890 1
							$this->logger->notice("Type-subtype '$paired_type:$paired_subtype' does not exist!");
891
							// return false if we're all invalid subtypes in the only valid type
892 3
							continue;
893
						}
894
					}
895
896
					// return false if there are no valid subtypes.
897 3
					if ($valid_pairs_subtypes_count <= 0) {
898
						return false;
899
					}
900
901
902 3
					if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
903 3
						$wheres[] = "({$table}.type = '$paired_type'"
904 3
								. " AND {$table}.subtype IN ($paired_subtype_ids_str))";
905
					}
906
				} else {
907 3
					$wheres[] = "({$table}.type = '$paired_type')";
908
				}
909
			}
910
		}
911
912
		// pairs override the above.  return false if they don't exist.
913 504 View Code Duplication
		if (is_array($wheres) && count($wheres)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
914 504
			$where = implode(' OR ', $wheres);
915 504
			return "($where)";
916
		}
917
918
		return '';
919
	}
920
921
	/**
922
	 * Returns SQL where clause for owner and containers.
923
	 *
924
	 * @param string     $column Column name the guids should be checked against. Usually
925
	 *                           best to provide in table.column format.
926
	 * @param null|array $guids  Array of GUIDs.
927
	 *
928
	 * @return false|string
929
	 * @access private
930
	 */
931 3705
	public function getGuidBasedWhereSql($column, $guids) {
932
		// short circuit if nothing requested
933
		// 0 is a valid guid
934 3705
		if (!$guids && $guids !== 0) {
935 3705
			return '';
936
		}
937
938
		// normalize and sanitise owners
939 3631
		if (!is_array($guids)) {
940 5
			$guids = [$guids];
941
		}
942
943 3631
		$guids_sanitized = [];
944 3631
		foreach ($guids as $guid) {
945 3631
			if ($guid !== ELGG_ENTITIES_NO_VALUE) {
946 3631
				$guid = sanitise_int($guid);
0 ignored issues
show
Deprecated Code introduced by
The function sanitise_int() 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...
947
948 3631
				if (!$guid) {
949 1
					return false;
950
				}
951
			}
952 3631
			$guids_sanitized[] = $guid;
953
		}
954
955 3631
		$where = '';
956 3631
		$guid_str = implode(',', $guids_sanitized);
957
958
		// implode(',', 0) returns 0.
959 3631
		if ($guid_str !== false && $guid_str !== '') {
960 3631
			$where = "($column IN ($guid_str))";
961
		}
962
963 3631
		return $where;
964
	}
965
966
	/**
967
	 * Returns SQL where clause for entity time limits.
968
	 *
969
	 * @param string   $table              Entity table prefix as defined in
970
	 *                                     SELECT...FROM entities $table
971
	 * @param null|int $time_created_upper Time created upper limit
972
	 * @param null|int $time_created_lower Time created lower limit
973
	 * @param null|int $time_updated_upper Time updated upper limit
974
	 * @param null|int $time_updated_lower Time updated lower limit
975
	 *
976
	 * @return false|string false on fail, string on success.
977
	 * @access private
978
	 */
979 3705
	public function getEntityTimeWhereSql($table, $time_created_upper = null,
980
	$time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) {
981
982 3705
		$wheres = [];
983
984
		// exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
985 3705
		if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
1 ignored issue
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...
Deprecated Code introduced by
The function sanitise_int() 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...
986 1
			$wheres[] = "{$table}.time_created <= $time_created_upper";
987
		}
988
989 3705
		if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
1 ignored issue
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...
Deprecated Code introduced by
The function sanitise_int() 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...
990 1
			$wheres[] = "{$table}.time_created >= $time_created_lower";
991
		}
992
993 3705
		if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
1 ignored issue
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...
Deprecated Code introduced by
The function sanitise_int() 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...
994
			$wheres[] = "{$table}.time_updated <= $time_updated_upper";
995
		}
996
997 3705
		if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
1 ignored issue
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...
Deprecated Code introduced by
The function sanitise_int() 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...
998
			$wheres[] = "{$table}.time_updated >= $time_updated_lower";
999
		}
1000
1001 3705 View Code Duplication
		if (is_array($wheres) && count($wheres) > 0) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1002 1
			$where_str = implode(' AND ', $wheres);
1003 1
			return "($where_str)";
1004
		}
1005
1006 3705
		return '';
1007
	}
1008
1009
	/**
1010
	 * Returns a list of months in which entities were updated or created.
1011
	 *
1012
	 * @tip Use this to generate a list of archives by month for when entities were added or updated.
1013
	 *
1014
	 * @todo document how to pass in array for $subtype
1015
	 *
1016
	 * @warning Months are returned in the form YYYYMM.
1017
	 *
1018
	 * @param string $type           The type of entity
1019
	 * @param string $subtype        The subtype of entity
1020
	 * @param int    $container_guid The container GUID that the entities belong to
1021
	 * @param string $order_by       Order_by SQL order by clause
1022
	 *
1023
	 * @return array|false Either an array months as YYYYMM, or false on failure
1024
	 */
1025
	public function getDates($type = '', $subtype = '', $container_guid = 0, $order_by = 'time_created') {
1026
1027
		$where = [];
1028
1029
		if ($type != "") {
1030
			$type = sanitise_string($type);
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...
1031
			$where[] = "type='$type'";
1032
		}
1033
1034
		if (is_array($subtype)) {
1035
			$tempwhere = "";
1036
			if (sizeof($subtype)) {
1037
				foreach ($subtype as $typekey => $subtypearray) {
1038
					foreach ($subtypearray as $subtypeval) {
1039
						$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...
1040
						if (!empty($subtypeval)) {
1041
							if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
1042
								return false;
1043
							}
1044
						} else {
1045
							$subtypeval = 0;
1046
						}
1047
						if (!empty($tempwhere)) {
1048
							$tempwhere .= " or ";
1049
						}
1050
						$tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
1051
					}
1052
				}
1053
			}
1054
			if (!empty($tempwhere)) {
1055
				$where[] = "({$tempwhere})";
1056
			}
1057
		} else {
1058
			if ($subtype) {
1059
				if (!$subtype_id = get_subtype_id($type, $subtype)) {
1060
					return false;
1061
				} else {
1062
					$where[] = "subtype=$subtype_id";
1063
				}
1064
			}
1065
		}
1066
1067
		if ($container_guid !== 0) {
1068
			if (is_array($container_guid)) {
1069
				foreach ($container_guid as $key => $val) {
1070
					$container_guid[$key] = (int) $val;
1071
				}
1072
				$where[] = "container_guid in (" . implode(",", $container_guid) . ")";
1073
			} else {
1074
				$container_guid = (int) $container_guid;
1075
				$where[] = "container_guid = {$container_guid}";
1076
			}
1077
		}
1078
1079
		$where[] = _elgg_get_access_where_sql(['table_alias' => '']);
1080
1081
		$sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
1082
			FROM {$this->db->prefix}entities where ";
1083
1084
		foreach ($where as $w) {
1085
			$sql .= " $w and ";
1086
		}
1087
1088
		$sql .= "1=1 ORDER BY $order_by";
1089
		if ($result = $this->db->getData($sql)) {
1090
			$endresult = [];
1091
			foreach ($result as $res) {
1092
				$endresult[] = $res->yearmonth;
1093
			}
1094
			return $endresult;
1095
		}
1096
		return false;
1097
	}
1098
1099
	/**
1100
	 * Update the last_action column in the entities table for $guid.
1101
	 *
1102
	 * @warning This is different to time_updated.  Time_updated is automatically set,
1103
	 * while last_action is only set when explicitly called.
1104
	 *
1105
	 * @param ElggEntity $entity Entity annotation|relationship action carried out on
1106
	 * @param int        $posted Timestamp of last action
1107
	 * @return int
1108
	 * @access private
1109
	 */
1110 10
	public function updateLastAction(ElggEntity $entity, $posted = null) {
1111
1112 10
		if (!$posted) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $posted of type integer|null is loosely compared to false; 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...
1113 2
			$posted = $this->getCurrentTime()->getTimestamp();
1114
		}
1115
		
1116
		$query = "
1117 10
			UPDATE {$this->db->prefix}entities
1118
			SET last_action = :last_action
1119
			WHERE guid = :guid
1120
		";
1121
1122
		$params = [
1123 10
			':last_action' => (int) $posted,
1124 10
			':guid' => (int) $entity->guid,
1125
		];
1126
		
1127 10
		$this->db->updateData($query, true, $params);
1128
1129 10
		return (int) $posted;
1130
	}
1131
1132
	/**
1133
	 * Get a user by GUID even if the entity is hidden or disabled
1134
	 *
1135
	 * @param int $guid User GUID. Default is logged in user
1136
	 *
1137
	 * @return ElggUser|false
1138
	 * @throws UserFetchFailureException
1139
	 * @access private
1140
	 */
1141 3711
	public function getUserForPermissionsCheck($guid = 0) {
1142 3711
		if (!$guid) {
1143 3705
			return $this->session->getLoggedInUser();
1144
		}
1145
1146
		// need to ignore access and show hidden entities for potential hidden/disabled users
1147 3625
		$ia = $this->session->setIgnoreAccess(true);
1148 3625
		$show_hidden = access_show_hidden_entities(true);
1149
	
1150 3625
		$user = $this->get($guid, 'user');
1151
1152 3625
		$this->session->setIgnoreAccess($ia);
1153 3625
		access_show_hidden_entities($show_hidden);
1154
1155 3625
		if (!$user) {
1156
			// requested to check access for a specific user_guid, but there is no user entity, so the caller
1157
			// should cancel the check and return false
1158 3418
			$message = $this->translator->translate('UserFetchFailureException', [$guid]);
1159
			// $this->logger->warn($message);
1160
1161 3418
			throw new UserFetchFailureException($message);
1162
		}
1163
1164 369
		return $user;
1165
	}
1166
1167
	/**
1168
	 * Disables all entities owned and contained by a user (or another entity)
1169
	 *
1170
	 * @param int $owner_guid The owner GUID
1171
	 * @return bool
1172
	 */
1173
	public function disableEntities($owner_guid) {
1174
		$entity = get_entity($owner_guid);
1175
		if (!$entity || !$entity->canEdit()) {
1176
			return false;
1177
		}
1178
1179
		if (!$this->events->trigger('disable', $entity->type, $entity)) {
1180
			return false;
1181
		}
1182
1183
		$query = "
1184
			UPDATE {$this->table}entities
1185
			SET enabled='no'
1186
			WHERE owner_guid = :owner_guid
1187
			OR container_guid = :owner_guid";
1188
1189
		$params = [
1190
			':owner_guid' => (int) $owner_guid,
1191
		];
1192
1193
		_elgg_invalidate_cache_for_entity($entity->guid);
1194
		_elgg_invalidate_memcache_for_entity($entity->guid);
1195
		
1196
		if ($this->db->updateData($query, true, $params)) {
1197
			return true;
1198
		}
1199
1200
		return false;
1201
	}
1202
1203
}
1204