Completed
Push — master ( d53197...b3a167 )
by Jeroen
73:00 queued 65:45
created

ElggEntity::getMetadata()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 17
nc 6
nop 1
dl 0
loc 29
ccs 14
cts 14
cp 1
crap 6
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
use Elgg\EntityIcon;
4
5
/**
6
 * The parent class for all Elgg Entities.
7
 *
8
 * An \ElggEntity is one of the basic data models in Elgg.
9
 * It is the primary means of storing and retrieving data from the database.
10
 * An \ElggEntity represents one row of the entities table.
11
 *
12
 * The \ElggEntity class handles CRUD operations for the entities table.
13
 * \ElggEntity should always be extended by another class to handle CRUD
14
 * operations on the type-specific table.
15
 *
16
 * \ElggEntity uses magic methods for get and set, so any property that isn't
17
 * declared will be assumed to be metadata and written to the database
18
 * as metadata on the object.  All children classes must declare which
19
 * properties are columns of the type table or they will be assumed
20
 * to be metadata.  See \ElggObject::initializeAttributes() for examples.
21
 *
22
 * Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and \ElggSite.
23
 *
24
 * @tip Plugin authors will want to extend the \ElggObject class, not this class.
25
 *
26
 * @package    Elgg.Core
27
 * @subpackage DataModel.Entities
28
 *
29
 * @property       string $type           object, user, group, or site (read-only after save)
30
 * @property       string $subtype        Further clarifies the nature of the entity
31
 * @property-read  int    $guid           The unique identifier for this entity (read only)
32
 * @property       int    $owner_guid     The GUID of the owner of this entity (usually the creator)
33
 * @property       int    $container_guid The GUID of the entity containing this entity
34
 * @property       int    $access_id      Specifies the visibility level of this entity
35
 * @property       int    $time_created   A UNIX timestamp of when the entity was created
36
 * @property-read  int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save)
37
 * @property-read  int    $last_action    A UNIX timestamp of when the entity was last acted upon
38
 * @property       string $enabled        Is this entity enabled ('yes' or 'no')
39
 *
40
 * Metadata (the above are attributes)
41
 * @property       string $location       A location of the entity
42
 */
43
abstract class ElggEntity extends \ElggData implements
44
	Locatable, // Geocoding interface
45
	EntityIcon // Icon interface
46
{
47
48
	public static $primary_attr_names = [
49
		'guid',
50
		'type',
51
		'subtype',
52
		'owner_guid',
53
		'container_guid',
54
		'access_id',
55
		'time_created',
56
		'time_updated',
57
		'last_action',
58
		'enabled',
59
	];
60
61
	protected static $integer_attr_names = [
62
		'guid',
63
		'owner_guid',
64
		'container_guid',
65
		'access_id',
66
		'time_created',
67
		'time_updated',
68
		'last_action',
69
	];
70
71
	/**
72
	 * Holds metadata until entity is saved.  Once the entity is saved,
73
	 * metadata are written immediately to the database.
74
	 * @var array
75
	 */
76
	protected $temp_metadata = [];
77
78
	/**
79
	 * Holds annotations until entity is saved.  Once the entity is saved,
80
	 * annotations are written immediately to the database.
81
	 * @var array
82
	 */
83
	protected $temp_annotations = [];
84
85
	/**
86
	 * Holds private settings until entity is saved. Once the entity is saved,
87
	 * private settings are written immediately to the database.
88
	 * @var array
89
	 */
90
	protected $temp_private_settings = [];
91
92
	/**
93
	 * Volatile data structure for this object, allows for storage of data
94
	 * in-memory that isn't sync'd back to the metadata table.
95
	 * @var array
96
	 */
97
	protected $volatile = [];
98
99
	/**
100
	 * Holds the original (persisted) attribute values that have been changed but not yet saved.
101
	 * @var array
102
	 */
103
	protected $orig_attributes = [];
104
105
	/**
106
	 * @var bool
107
	 */
108
	protected $_is_cacheable = true;
109
110
	/**
111
	 * Create a new entity.
112
	 *
113
	 * Plugin developers should only use the constructor to create a new entity.
114
	 * To retrieve entities, use get_entity() and the elgg_get_entities* functions.
115
	 *
116
	 * If no arguments are passed, it creates a new entity.
117
	 * If a database result is passed as a \stdClass instance, it instantiates
118
	 * that entity.
119
	 *
120
	 * @param stdClass $row Database row result. Default is null to create a new object.
121
	 *
122
	 * @throws IOException If cannot load remaining data from db
123
	 */
124 5322
	public function __construct(stdClass $row = null) {
125 5322
		$this->initializeAttributes();
126
127 5322
		if ($row && !$this->load($row)) {
128
			$msg = "Failed to load new " . get_class() . " for GUID:" . $row->guid;
129
			throw new \IOException($msg);
130
		}
131 5322
	}
132
133
	/**
134
	 * Initialize the attributes array.
135
	 *
136
	 * This is vital to distinguish between metadata and base parameters.
137
	 *
138
	 * @return void
139
	 */
140 5322
	protected function initializeAttributes() {
141 5322
		parent::initializeAttributes();
142
143 5322
		$this->attributes['guid'] = null;
144 5322
		$this->attributes['type'] = null;
145 5322
		$this->attributes['subtype'] = null;
146
147 5322
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
148 5322
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
149
150 5322
		$this->attributes['access_id'] = ACCESS_PRIVATE;
151 5322
		$this->attributes['time_updated'] = null;
152 5322
		$this->attributes['last_action'] = null;
153 5322
		$this->attributes['enabled'] = "yes";
154
155 5322
		$this->attributes['type'] = $this->getType();
156 5322
	}
157
158
	/**
159
	 * Clone an entity
160
	 *
161
	 * Resets the guid so that the entity can be saved as a distinct entity from
162
	 * the original. Creation time will be set when this new entity is saved.
163
	 * The owner and container guids come from the original entity. The clone
164
	 * method copies metadata but does not copy annotations or private settings.
165
	 *
166
	 * @note metadata will have its owner and access id set when the entity is saved
167
	 * and it will be the same as that of the entity.
168
	 *
169
	 * @return void
170
	 */
171 1
	public function __clone() {
172 1
		$orig_entity = get_entity($this->guid);
173 1
		if (!$orig_entity) {
174
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
175
			return;
176
		}
177
178 1
		$metadata_array = elgg_get_metadata([
179 1
			'guid' => $this->guid,
180 1
			'limit' => 0
181
		]);
182
183 1
		$this->attributes['guid'] = null;
184 1
		$this->attributes['time_created'] = null;
185 1
		$this->attributes['time_updated'] = null;
186 1
		$this->attributes['last_action'] = null;
187
188 1
		$this->attributes['subtype'] = $orig_entity->getSubtype();
189
190
		// copy metadata over to new entity - slightly convoluted due to
191
		// handling of metadata arrays
192 1
		if (is_array($metadata_array)) {
193
			// create list of metadata names
194 1
			$metadata_names = [];
195 1
			foreach ($metadata_array as $metadata) {
196 1
				$metadata_names[] = $metadata['name'];
197
			}
198
			// arrays are stored with multiple enties per name
199 1
			$metadata_names = array_unique($metadata_names);
200
201
			// move the metadata over
202 1
			foreach ($metadata_names as $name) {
203 1
				$this->__set($name, $orig_entity->$name);
204
			}
205
		}
206 1
	}
207
208
	/**
209
	 * Set an attribute or metadata value for this entity
210
	 *
211
	 * Anything that is not an attribute is saved as metadata.
212
	 *
213
	 * @warning Metadata set this way will inherit the entity's owner and
214
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
215
	 *
216
	 * @param string $name  Name of the attribute or metadata
217
	 * @param mixed  $value The value to be set
218
	 * @return void
219
	 * @see \ElggEntity::setMetadata()
220
	 */
221 1092
	public function __set($name, $value) {
222 1092
		if ($this->$name === $value) {
223
			// quick return if value is not changing
224 289
			return;
225
		}
226
227
		// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
228
		// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
229
		// the value to empty string, don't consider this a change.
230 1092
		if (in_array($name, ['title', 'name', 'description'])
231 1092
			&& $this->$name === null
232 1092
			&& $value === "") {
233
			return;
234
		}
235
236 1092
		if (array_key_exists($name, $this->attributes)) {
237
			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
238 589
			if (is_int($this->attributes[$name])
239 589
					&& is_string($value)
240 589
					&& ((string) $this->attributes[$name] === $value)) {
241 1
				return;
242
			}
243
244
			// keep original values
245 589
			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
246 9
				$this->orig_attributes[$name] = $this->attributes[$name];
247
			}
248
249
			// Certain properties should not be manually changed!
250 589
			switch ($name) {
251
				case 'guid':
252
				case 'time_updated':
253
				case 'last_action':
254
					return;
255
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
256
				case 'access_id':
257
				case 'owner_guid':
258
				case 'container_guid':
259 482
					if ($value !== null) {
260 482
						$this->attributes[$name] = (int) $value;
261
					} else {
262
						$this->attributes[$name] = null;
263
					}
264 482
					break;
265
				default:
266 443
					$this->attributes[$name] = $value;
267 443
					break;
268
			}
269 589
			return;
270
		}
271
272 1062
		$this->setMetadata($name, $value);
273 1062
	}
274
275
	/**
276
	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
277
	 *
278
	 * @return array
279
	 */
280 59
	public function getOriginalAttributes() {
281 59
		return $this->orig_attributes;
282
	}
283
284
	/**
285
	 * Get an attribute or metadata value
286
	 *
287
	 * If the name matches an attribute, the attribute is returned. If metadata
288
	 * does not exist with that name, a null is returned.
289
	 *
290
	 * This only returns an array if there are multiple values for a particular
291
	 * $name key.
292
	 *
293
	 * @param string $name Name of the attribute or metadata
294
	 * @return mixed
295
	 */
296 5387
	public function __get($name) {
297 5387
		if (array_key_exists($name, $this->attributes)) {
298 5386
			return $this->attributes[$name];
299
		}
300
301 5363
		return $this->getMetadata($name);
302
	}
303
304
	/**
305
	 * Get the entity's display name
306
	 *
307
	 * @return string The title or name of this entity.
308
	 */
309 19
	public function getDisplayName() {
310 19
		return $this->name;
311
	}
312
313
	/**
314
	 * Sets the title or name of this entity.
315
	 *
316
	 * @param string $display_name The title or name of this entity.
317
	 * @return void
318
	 */
319
	public function setDisplayName($display_name) {
320
		$this->name = $display_name;
1 ignored issue
show
Bug Best Practice introduced by
The property name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
321
	}
322
323
	/**
324
	 * Return the value of a piece of metadata.
325
	 *
326
	 * @param string $name Name
327
	 *
328
	 * @return mixed The value, or null if not found.
329
	 */
330 5363
	public function getMetadata($name) {
331 5363
		$guid = $this->guid;
332
333 5363
		if (!$guid) {
334 572
			if (isset($this->temp_metadata[$name])) {
335
				// md is returned as an array only if more than 1 entry
336 285
				if (count($this->temp_metadata[$name]) == 1) {
337 285
					return $this->temp_metadata[$name][0];
338
				} else {
339 158
					return $this->temp_metadata[$name];
340
				}
341
			} else {
342 572
				return null;
343
			}
344
		}
345
346
		// upon first cache miss, just load/cache all the metadata and retry.
347
		// if this works, the rest of this function may not be needed!
348 5354
		$cache = _elgg_services()->metadataCache;
349 5354
		if ($cache->isLoaded($guid)) {
350 2238
			return $cache->getSingle($guid, $name);
351
		} else {
352 4136
			$cache->populateFromEntities([$guid]);
353
			// in case ignore_access was on, we have to check again...
354 4136
			if ($cache->isLoaded($guid)) {
355 1082
				return $cache->getSingle($guid, $name);
356
			}
357
358
			return null;
359 4083
		}
360 4083
	}
361 4083
362 4083
	/**
363
	 * Unset a property from metadata or attribute.
364
	 *
365
	 * @warning If you use this to unset an attribute, you must save the object!
366 4083
	 *
367
	 * @param string $name The name of the attribute or metadata.
368 4083
	 *
369
	 * @return void
370 4083
	 * @todo some attributes should be set to null or other default values
371
	 */
372 4083
	public function __unset($name) {
373
		if (array_key_exists($name, $this->attributes)) {
374
			$this->attributes[$name] = "";
375
		} else {
376 4083
			$this->deleteMetadata($name);
377
		}
378
	}
379
380
	/**
381
	 * Set metadata on this entity.
382
	 *
383
	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
384
	 * Use this method if you want to explicitly set the owner or access of the metadata.
385
	 * You cannot set the owner/access before the entity has been saved.
386
	 *
387
	 * @param string $name       Name of the metadata
388
	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
389 49
	 * @param string $value_type 'text', 'integer', or '' for automatic detection
390 49
	 * @param bool   $multiple   Allow multiple values for a single name.
391
	 *                           Does not support associative arrays.
392
	 *
393 49
	 * @return bool
394
	 * @throws InvalidArgumentException
395 49
	 */
396
	public function setMetadata($name, $value, $value_type = '', $multiple = false) {
397
398
		// normalize value to an array that we will loop over
399
		// remove indexes if value already an array.
400
		if (is_array($value)) {
401
			$value = array_values($value);
402
		} else {
403
			$value = [$value];
404
		}
405
406
		// strip null values from array
407
		$value = array_filter($value, function($var) {
408
			return !is_null($var);
409
		});
410
411
		if (empty($this->guid)) {
412
			// unsaved entity. store in temp array
413 1062
			return $this->setTempMetadata($name, $value, $multiple);
414
		}
415
416
		// saved entity. persist md to db.
417 1062
		if (!$multiple) {
418 162
			$current_metadata = $this->getMetadata($name);
419
420 1061
			if ((is_array($current_metadata) || count($value) > 1 || $value === []) && isset($current_metadata)) {
421
				// remove current metadata if needed
422
				// need to remove access restrictions right now to delete
423
				// because this is the expected behavior
424 1062
				$ia = elgg_set_ignore_access(true);
425 1062
				$delete_result = elgg_delete_metadata([
426 1062
					'guid' => $this->guid,
427
					'metadata_name' => $name,
428 1062
					'limit' => false,
429
				]);
430 569
				elgg_set_ignore_access($ia);
431
432
				if (false === $delete_result) {
433
					return false;
434 1028
				}
435 1028
			}
436
437 1028
			if (count($value) > 1) {
438
				// new value is a multiple valued metadata
439
				$multiple = true;
440
			}
441 8
		}
442 8
443 8
		// create new metadata
444 8
		foreach ($value as $value_tmp) {
445
			$metadata = new ElggMetadata();
446
			$metadata->entity_guid = $this->guid;
447 8
			$metadata->name = $name;
448
			$metadata->value_type = $value_type;
449 8
			$metadata->value = $value_tmp;
450
			$md_id = _elgg_services()->metadataTable->create($metadata, $multiple);
451
			if (!$md_id) {
452
				return false;
453
			}
454 1028
		}
455
456 162
		return true;
457
	}
458
459
	/**
460
	 * Set temp metadata on this entity.
461 1028
	 *
462 1028
	 * @param string $name     Name of the metadata
463 1028
	 * @param mixed  $value    Value of the metadata (doesn't support assoc arrays)
464 1028
	 * @param bool   $multiple Allow multiple values for a single name.
465 1028
	 *                         Does not support associative arrays.
466 1028
	 *
467 1028
	 * @return bool
468 1028
	 */
469 1028
	protected function setTempMetadata($name, $value, $multiple = false) {
470
		// if overwrite, delete first
471
		if (!$multiple) {
472
			unset($this->temp_metadata[$name]);
473 1028
			if (count($value)) {
474
				// only save if value array contains data
475
				$this->temp_metadata[$name] = $value;
476
			}
477
			return true;
478
		}
479
480
		if (!isset($this->temp_metadata[$name])) {
481
			$this->temp_metadata[$name] = [];
482
		}
483
484
		$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
485
486 569
		return true;
487
	}
488 569
489 569
490 569
491
	/**
492 569
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
493
	 * If you pass a name, only metadata matching that name will be deleted.
494 569
	 *
495
	 * @warning Calling this with no $name will clear all metadata on the entity.
496
	 *
497 3
	 * @param null|string $name The name of the metadata to remove.
498
	 * @return bool|null
499
	 * @since 1.8
500
	 */
501 3
	public function deleteMetadata($name = null) {
502
503 3
		if (!$this->guid) {
504
			// remove from temp_metadata
505
			if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string 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...
506
				if (!isset($this->temp_metadata[$name])) {
507
					return null;
508
				} else {
509
					unset($this->temp_metadata[$name]);
510
					return true;
511
				}
512
			} else {
513
				$this->temp_metadata = [];
514
				return true;
515
			}
516
		}
517
518 266
		$options = [
519
			'guid' => $this->guid,
520 266
			'limit' => 0
521
		];
522 2
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string 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...
523 2
			$options['metadata_name'] = $name;
524
		}
525
526 2
		return elgg_delete_metadata($options);
527 2
	}
528
529
	/**
530
	 * Get a piece of volatile (non-persisted) data on this entity.
531
	 *
532
	 * @param string $name The name of the volatile data
533
	 *
534
	 * @return mixed The value or null if not found.
535
	 */
536 266
	public function getVolatileData($name) {
537 266
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
538
	}
539 266
540 55
	/**
541
	 * Set a piece of volatile (non-persisted) data on this entity
542
	 *
543 266
	 * @param string $name  Name
544
	 * @param mixed  $value Value
545
	 *
546
	 * @return void
547
	 */
548
	public function setVolatileData($name, $value) {
549
		$this->volatile[$name] = $value;
550
	}
551
552
	/**
553 25
	 * Remove all relationships to and from this entity.
554 25
	 * If you pass a relationship name, only relationships matching that name
555
	 * will be deleted.
556
	 *
557
	 * @warning Calling this with no $relationship will clear all relationships
558
	 * for this entity.
559
	 *
560
	 * @param null|string $relationship The name of the relationship to remove.
561
	 * @return bool
562
	 * @see \ElggEntity::addRelationship()
563
	 * @see \ElggEntity::removeRelationship()
564
	 */
565 26
	public function deleteRelationships($relationship = null) {
566 26
		$relationship = (string) $relationship;
567 26
		$result = remove_entity_relationships($this->getGUID(), $relationship);
568
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
569
	}
570
571
	/**
572
	 * Add a relationship between this an another entity.
573
	 *
574
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
575
	 *
576
	 * @param int    $guid_two     GUID of the target entity of the relationship.
577
	 * @param string $relationship The type of relationship.
578
	 *
579
	 * @return bool
580
	 * @see \ElggEntity::removeRelationship()
581
	 * @see \ElggEntity::deleteRelationships()
582 214
	 */
583 214
	public function addRelationship($guid_two, $relationship) {
584 214
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
585 214
	}
586
587
	/**
588
	 * Remove a relationship
589
	 *
590
	 * @param int    $guid_two     GUID of the target entity of the relationship.
591
	 * @param string $relationship The type of relationship.
592
	 *
593
	 * @return bool
594
	 * @see \ElggEntity::addRelationship()
595
	 * @see \ElggEntity::deleteRelationships()
596
	 */
597
	public function removeRelationship($guid_two, $relationship) {
598
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
599
	}
600 5
601 5
	/**
602
	 * Adds a private setting to this entity.
603
	 *
604
	 * @warning Private settings are stored as strings
605
	 *          Unlike metadata and annotations there is no reverse casting when you retrieve the setting
606
	 *          When saving integers, they will be cast to strings
607
	 *          Booleans will be cast to '0' and '1'
608
	 *
609
	 * @param string $name  Name of private setting
610
	 * @param mixed  $value Value of private setting
611
	 *
612
	 * @return bool
613
	 * @throws DatabaseException
614 1
	 */
615 1
	public function setPrivateSetting($name, $value) {
616
		if (is_bool($value)) {
617
			$value = (int) $value;
618
		}
619
620
		if (!$this->guid) {
621
			$this->temp_private_settings[$name] = $value;
622
623
			return true;
624
		}
625
626
		return _elgg_services()->privateSettings->set($this, $name, $value);
627
	}
628
629 124
	/**
630 124
	 * Returns a private setting value
631 123
	 *
632
	 * @warning Private settings are always returned as strings
633 45
	 *          Make sure you can your values back to expected type
634 45
	 *
635
	 * @param string $name Name of the private setting
636
	 *
637
	 * @return string|null
638
	 * @throws DatabaseException
639
	 */
640
	public function getPrivateSetting($name) {
641
		if (!$this->guid) {
642
			return elgg_extract($name, $this->temp_private_settings);
643
		}
644
645 37
		return _elgg_services()->privateSettings->get($this, $name);
646 37
	}
647 37
648
	/**
649 5
	 * Returns all private settings
650 5
	 *
651
	 * @return array
652
	 * @throws DatabaseException
653 5
	 */
654
	public function getAllPrivateSettings() {
655
		if (!$this->guid) {
656
			return $this->temp_private_settings;
657
		}
658
659
		return _elgg_services()->privateSettings->getAllForEntity($this);
660
	}
661
662
	/**
663 3
	 * Removes private setting
664 3
	 *
665
	 * @param string $name Name of the private setting
666
	 *
667
	 * @return bool
668
	 * @throws DatabaseException
669
	 */
670
	public function removePrivateSetting($name) {
671
		if (!$this->guid) {
672
			unset($this->temp_private_settings[$name]);
673
			return true;
674
		}
675
676
		return _elgg_services()->privateSettings->remove($this, $name);
677 214
	}
678
679 214
	/**
680 214
	 * Removes all private settings
681
	 *
682 214
	 * @return bool
683 1
	 * @throws DatabaseException
684
	 */
685
	public function removeAllPrivateSettings() {
686 214
		if (!$this->guid) {
687
			$this->temp_private_settings = [];
688
			return true;
689
		}
690
691
		return _elgg_services()->privateSettings->removeAllForEntity($this);
692
	}
693
694
	/**
695
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
696
	 * If you pass a name, only annotations matching that name will be deleted.
697 214
	 *
698
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
699
	 *
700 214
	 * @param null|string $name The annotations name to remove.
701
	 * @return bool
702 214
	 * @since 1.8
703 214
	 */
704
	public function deleteAnnotations($name = null) {
705 214
		$options = [
706
			'guid' => $this->guid,
707
			'limit' => 0
708
		];
709 214
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string 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...
710 214
			$options['annotation_name'] = $name;
711 214
		}
712
713
		return elgg_delete_annotations($options);
714
	}
715
716
	/**
717
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
718
	 * If you pass a name, only annotations matching that name will be deleted.
719
	 *
720
	 * @param null|string $name The name of annotations to delete.
721 5
	 * @return bool
722
	 * @since 1.8
723 5
	 */
724 5
	public function deleteOwnedAnnotations($name = null) {
725
		// access is turned off for this because they might
726 5
		// no longer have access to an entity they created annotations on.
727
		$ia = elgg_set_ignore_access(true);
728
		$options = [
729
			'annotation_owner_guid' => $this->guid,
730 5
			'limit' => 0
731
		];
732
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string 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...
733
			$options['annotation_name'] = $name;
734
		}
735
736
		$r = elgg_delete_annotations($options);
737
		elgg_set_ignore_access($ia);
738
		return $r;
739
	}
740
741
	/**
742 3
	 * Disables annotations for this entity, optionally based on name.
743
	 *
744 3
	 * @param string $name An options name of annotations to disable.
745 3
	 * @return bool
746
	 * @since 1.8
747 3
	 */
748
	public function disableAnnotations($name = '') {
749
		$options = [
750
			'guid' => $this->guid,
751 3
			'limit' => 0
752
		];
753
		if ($name) {
754
			$options['annotation_name'] = $name;
755
		}
756
757
		return elgg_disable_annotations($options);
758
	}
759
760
	/**
761 2
	 * Enables annotations for this entity, optionally based on name.
762
	 *
763 2
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
764
	 *
765 2
	 * @param string $name An options name of annotations to enable.
766 2
	 * @return bool
767
	 * @since 1.8
768
	 */
769 2
	public function enableAnnotations($name = '') {
770
		$options = [
771
			'guid' => $this->guid,
772
			'limit' => 0
773
		];
774
		if ($name) {
775
			$options['annotation_name'] = $name;
776
		}
777
778
		return elgg_enable_annotations($options);
779
	}
780
781
	/**
782
	 * Helper function to return annotation calculation results
783
	 *
784
	 * @param string $name        The annotation name.
785
	 * @param string $calculation A valid MySQL function to run its values through
786
	 * @return mixed
787
	 */
788
	private function getAnnotationCalculation($name, $calculation) {
789
		$options = [
790
			'guid' => $this->getGUID(),
791 93
			'distinct' => false,
792 93
			'annotation_name' => $name,
793 93
			'annotation_calculation' => $calculation
794 92
		];
795
796 93
		return elgg_get_annotations($options);
797 93
	}
798 93
799 93
	/**
800 93
	 * Adds an annotation to an entity.
801 93
	 *
802 93
	 * @warning By default, annotations are private.
803 93
	 *
804
	 * @warning Annotating an unsaved entity more than once with the same name
805 17
	 *          will only save the last annotation.
806
	 *
807 17
	 * @todo Update temp_annotations to store an instance of ElggAnnotation and simply call ElggAnnotation::save(),
808
	 *       after entity is saved
809
	 *
810
	 * @param string $name       Annotation name
811
	 * @param mixed  $value      Annotation value
812
	 * @param int    $access_id  Access ID
813
	 * @param int    $owner_guid GUID of the annotation owner
814
	 * @param string $value_type The type of annotation value
815
	 *
816
	 * @return bool|int Returns int if an annotation is saved
817
	 */
818
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $value_type = "") {
819
		if ($this->guid) {
820
			if (!$owner_guid) {
821 9
				$owner_guid = elgg_get_logged_in_user_guid();
822 9
			}
823 9
			$annotation = new ElggAnnotation();
824
			$annotation->entity_guid = $this->guid;
825 9
			$annotation->name = $name;
826
			$annotation->value_type = $value_type;
827
			$annotation->value = $value;
828
			$annotation->owner_guid = $owner_guid;
829
			$annotation->access_id = $access_id;
830
			return $annotation->save();
831
		} else {
832
			$this->temp_annotations[$name] = $value;
833
		}
834
		return true;
835
	}
836
837
	/**
838
	 * Gets an array of annotations.
839
	 *
840
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
841
	 * as the options array.
842
	 *
843
	 * @param array $options Array of options for elgg_get_annotations() except guid.
844 2
	 *
845 2
	 * @return array
846
	 * @see elgg_get_annotations()
847
	 */
848
	public function getAnnotations(array $options = []) {
849
		if ($this->guid) {
850
			$options['guid'] = $this->guid;
851
852
			return elgg_get_annotations($options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return elgg_get_annotations($options) also could return the type integer|false which is incompatible with the documented return type array.
Loading history...
853
		} else {
854
			$name = elgg_extract('annotation_name', $options, '');
855
856
			if (isset($this->temp_annotations[$name])) {
857
				return [$this->temp_annotations[$name]];
858
			}
859
		}
860
861
		return [];
862
	}
863
864
	/**
865
	 * Count annotations.
866
	 *
867
	 * @param string $name The type of annotation.
868
	 *
869
	 * @return int
870
	 */
871
	public function countAnnotations($name = "") {
872
		return $this->getAnnotationCalculation($name, 'count');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAnnotat...ulation($name, 'count') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
873
	}
874
875
	/**
876
	 * Get the average of an integer type annotation.
877
	 *
878
	 * @param string $name Annotation name
879
	 *
880
	 * @return int
881
	 */
882
	public function getAnnotationsAvg($name) {
883
		return $this->getAnnotationCalculation($name, 'avg');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAnnotat...lculation($name, 'avg') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
884
	}
885
886
	/**
887
	 * Get the sum of integer type annotations of a given name.
888
	 *
889
	 * @param string $name Annotation name
890
	 *
891
	 * @return int
892
	 */
893
	public function getAnnotationsSum($name) {
894
		return $this->getAnnotationCalculation($name, 'sum');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAnnotat...lculation($name, 'sum') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
895
	}
896
897
	/**
898 2
	 * Get the minimum of integer type annotations of given name.
899 2
	 *
900 2
	 * @param string $name Annotation name
901
	 *
902 2
	 * @return int
903
	 */
904
	public function getAnnotationsMin($name) {
905
		return $this->getAnnotationCalculation($name, 'min');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAnnotat...lculation($name, 'min') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
906 2
	}
907 2
908 2
	/**
909 2
	 * Get the maximum of integer type annotations of a given name.
910
	 *
911
	 * @param string $name Annotation name
912
	 *
913
	 * @return int
914
	 */
915
	public function getAnnotationsMax($name) {
916
		return $this->getAnnotationCalculation($name, 'max');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAnnotat...lculation($name, 'max') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
917
	}
918
919
	/**
920
	 * Count the number of comments attached to this entity.
921
	 *
922
	 * @return int Number of comments
923
	 * @since 1.8.0
924
	 */
925 218
	public function countComments() {
926 218
		$params = ['entity' => $this];
927 218
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $num is correct as _elgg_services()->hooks-...is->getType(), $params) targeting Elgg\PluginHooksService::trigger() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
928
929
		if (is_int($num)) {
930
			return $num;
931
		}
932
933
		return elgg_get_entities([
1 ignored issue
show
Bug Best Practice introduced by
The expression return elgg_get_entities..., 'distinct' => false)) also could return the type ElggEntity[]|false which is incompatible with the documented return type integer.
Loading history...
934
			'type' => 'object',
935
			'subtype' => 'comment',
936
			'container_guid' => $this->getGUID(),
937
			'count' => true,
938
			'distinct' => false,
939
		]);
940
	}
941
942
	/**
943
	 * Returns the ACLs owned by the entity
944
	 *
945
	 * @param array $options additional options to get the access collections with
946
	 *
947
	 * @return \ElggAccessCollection[]
948
	 *
949
	 * @see elgg_get_access_collections()
950
	 * @since 3.0
951
	 */
952
	public function getOwnedAccessCollections($options = []) {
953
		$options['owner_guid'] = $this->guid;
954
		return _elgg_services()->accessCollections->getEntityCollections($options);
955
	}
956
	
957
	/**
958
	 * Returns the first ACL owned by the entity with a given subtype
959
	 *
960
	 * @param string $subtype subtype of the ACL
961
	 *
962
	 * @return \ElggAccessCollection|false
963
	 *
964
	 * @since 3.0
965
	 */
966
	public function getOwnedAccessCollection($subtype) {
967
		if (!is_string($subtype) || $subtype === '') {
968
			return false;
969
		}
970
		
971
		$acls = $this->getOwnedAccessCollections([
972
			'subtype' => $subtype,
973
		]);
974
		
975
		return elgg_extract(0, $acls, false);
976
	}
977
978
	/**
979
	 * Gets an array of entities with a relationship to this entity.
980
	 *
981
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
982
	 *                       for a list of options. 'relationship_guid' is set to
983
	 *                       this entity.
984
	 *
985
	 * @return array|false An array of entities or false on failure
986
	 * @see elgg_get_entities_from_relationship()
987
	 */
988
	public function getEntitiesFromRelationship(array $options = []) {
989
		$options['relationship_guid'] = $this->guid;
990
		return elgg_get_entities($options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return elgg_get_entities($options) also could return the type integer which is incompatible with the documented return type false|array.
Loading history...
991
	}
992
993 399
	/**
994 399
	 * Gets the number of entities from a specific relationship type
995
	 *
996
	 * @param string $relationship         Relationship type (eg "friends")
997
	 * @param bool   $inverse_relationship Invert relationship
998
	 *
999
	 * @return int|false The number of entities or false on failure
1000
	 */
1001
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
1002
		return elgg_get_entities([
0 ignored issues
show
Bug Best Practice introduced by
The expression return elgg_get_entities...ship, 'count' => true)) also could return the type ElggEntity[] which is incompatible with the documented return type integer|false.
Loading history...
1003
			'relationship' => $relationship,
1004
			'relationship_guid' => $this->getGUID(),
1005
			'inverse_relationship' => $inverse_relationship,
1006
			'count' => true
1007
		]);
1008 462
	}
1009 462
1010
	/**
1011
	 * Can a user edit this entity?
1012
	 *
1013
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
1014
	 *
1015
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1016
	 *
1017
	 * @return bool Whether this entity is editable by the given user.
1018
	 * @see elgg_set_ignore_access()
1019
	 */
1020
	public function canEdit($user_guid = 0) {
1021
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
1022
	}
1023
1024
	/**
1025
	 * Can a user delete this entity?
1026
	 *
1027 338
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
1028 338
	 *
1029
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1030
	 *
1031
	 * @return bool Whether this entity is deletable by the given user.
1032
	 * @since 1.11
1033
	 * @see elgg_set_ignore_access()
1034
	 */
1035
	public function canDelete($user_guid = 0) {
1036
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
1037
	}
1038
1039
	/**
1040
	 * Can a user edit metadata on this entity?
1041 407
	 *
1042 407
	 * If no specific metadata is passed, it returns whether the user can
1043
	 * edit any metadata on the entity.
1044
	 *
1045
	 * @tip Can be overridden by by registering for the permissions_check:metadata
1046
	 * plugin hook.
1047
	 *
1048
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1049
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1050
	 *
1051
	 * @return bool
1052
	 * @see elgg_set_ignore_access()
1053
	 */
1054
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1055 4
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1056 4
	}
1057
1058
	/**
1059
	 * Can a user add an entity to this container
1060
	 *
1061
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1062
	 * @param string $type      The type of entity we're looking to write
1063
	 * @param string $subtype   The subtype of the entity we're looking to write
1064
	 *
1065
	 * @return bool
1066
	 * @see elgg_set_ignore_access()
1067
	 */
1068
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1069
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1070
	}
1071
1072
	/**
1073 8
	 * Can a user comment on an entity?
1074 8
	 *
1075
	 * @tip Can be overridden by registering for the permissions_check:comment,
1076
	 * <entity type> plugin hook.
1077
	 *
1078
	 * @param int  $user_guid User guid (default is logged in user)
1079
	 * @param bool $default   Default permission
1080
	 * @return bool
1081
	 */
1082
	public function canComment($user_guid = 0, $default = null) {
1083
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1084
	}
1085
1086
	/**
1087
	 * Can a user annotate an entity?
1088
	 *
1089
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1090
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1091 548
	 *
1092 548
	 * @tip If you want logged out users to annotate an object, do not call
1093
	 * canAnnotate(). It's easier than using the plugin hook.
1094
	 *
1095
	 * @param int    $user_guid       User guid (default is logged in user)
1096
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1097
	 *
1098
	 * @return bool
1099
	 */
1100 1
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1101
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1102 1
	}
1103
1104
	/**
1105
	 * Returns the access_id.
1106
	 *
1107
	 * @return int The access ID
1108
	 */
1109
	public function getAccessID() {
1110 566
		return $this->access_id;
1111 566
	}
1112
1113
	/**
1114
	 * Returns the guid.
1115
	 *
1116
	 * @return int|null GUID
1117
	 */
1118
	public function getGUID() {
1119 95
		return $this->guid;
1120 95
	}
1121
1122
	/**
1123
	 * Returns the entity type
1124
	 *
1125
	 * @return string The entity type
1126
	 */
1127
	public function getType() {
1128 414
		// this is just for the PHPUnit mocking framework
1129 414
		return $this->type;
1130
	}
1131
1132
	/**
1133
	 * Get the entity subtype
1134
	 *
1135
	 * @return string The entity subtype
1136
	 */
1137
	public function getSubtype() {
1138
		return $this->attributes['subtype'];
1139 1
	}
1140 1
1141
	/**
1142
	 * Get the guid of the entity's owner.
1143
	 *
1144
	 * @return int The owner GUID
1145
	 */
1146
	public function getOwnerGUID() {
1147
		return (int) $this->owner_guid;
1148 425
	}
1149 425
1150
	/**
1151
	 * Gets the \ElggEntity that owns this entity.
1152
	 *
1153
	 * @return \ElggEntity The owning entity
1154
	 */
1155
	public function getOwnerEntity() {
1156
		return get_entity($this->owner_guid);
1157
	}
1158 420
1159 420
	/**
1160
	 * Set the container for this object.
1161
	 *
1162
	 * @param int $container_guid The ID of the container.
1163
	 *
1164
	 * @return bool
1165
	 */
1166
	public function setContainerGUID($container_guid) {
1167 5
		return $this->container_guid = (int) $container_guid;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->container_... = (int)$container_guid returns the type integer which is incompatible with the documented return type boolean.
Loading history...
1168 5
	}
1169
1170
	/**
1171
	 * Gets the container GUID for this entity.
1172
	 *
1173
	 * @return int
1174
	 */
1175
	public function getContainerGUID() {
1176
		return (int) $this->container_guid;
1177
	}
1178
1179 42
	/**
1180 42
	 * Get the container entity for this object.
1181
	 *
1182 42
	 * @return \ElggEntity
1183 39
	 * @since 1.8.0
1184
	 */
1185
	public function getContainerEntity() {
1186 3
		return get_entity($this->getContainerGUID());
1187
	}
1188
1189
	/**
1190
	 * Returns the UNIX epoch time that this entity was last updated
1191
	 *
1192
	 * @return int UNIX epoch time
1193
	 */
1194
	public function getTimeUpdated() {
1195
		return $this->time_updated;
1196
	}
1197
1198
	/**
1199
	 * Gets the URL for this entity.
1200
	 *
1201
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1202
	 * customize the url for an entity.
1203
	 *
1204
	 * @return string The URL of the entity
1205
	 */
1206
	public function getURL() {
1207
		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $url is correct as _elgg_services()->hooks-...ray('entity' => $this)) targeting Elgg\PluginHooksService::trigger() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1208
1209
		if ($url === null || $url === '' || $url === false) {
1210
			return '';
1211
		}
1212
1213
		return elgg_normalize_url($url);
1214
	}
1215
1216
	/**
1217
	 * Saves icons using an uploaded file as the source.
1218
	 *
1219
	 * @param string $input_name Form input name
1220
	 * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1221
	 * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1222
	 * @return bool
1223
	 */
1224
	public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1225
		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1226
	}
1227
1228
	/**
1229
	 * Saves icons using a local file as the source.
1230
	 *
1231
	 * @param string $filename The full path to the local file
1232
	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1233 7
	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1234 7
	 * @return bool
1235
	 */
1236
	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1237
		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1238
	}
1239
1240
	/**
1241
	 * Saves icons using a file located in the data store as the source.
1242
	 *
1243 1
	 * @param string $file   An ElggFile instance
1244 1
	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1245
	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1246
	 * @return bool
1247
	 */
1248
	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1249
		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1250
	}
1251
1252
	/**
1253
	 * Returns entity icon as an ElggIcon object
1254
	 * The icon file may or may not exist on filestore
1255
	 *
1256
	 * @param string $size Size of the icon
1257
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1258
	 * @return \ElggIcon
1259
	 */
1260
	public function getIcon($size, $type = 'icon') {
1261
		return _elgg_services()->iconService->getIcon($this, $size, $type);
1262
	}
1263
1264
	/**
1265
	 * Removes all icon files and metadata for the passed type of icon.
1266 3
	 *
1267 3
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1268
	 * @return bool
1269
	 */
1270
	public function deleteIcon($type = 'icon') {
1271
		return _elgg_services()->iconService->deleteIcon($this, $type);
1272
	}
1273
1274
	/**
1275
	 * Returns the timestamp of when the icon was changed.
1276
	 *
1277
	 * @param string $size The size of the icon
1278
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1279
	 *
1280
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1281 12
	 */
1282 12
	public function getIconLastChange($size, $type = 'icon') {
1283
		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1284
	}
1285
1286
	/**
1287
	 * Returns if the entity has an icon of the passed type.
1288
	 *
1289
	 * @param string $size The size of the icon
1290 533
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1291 533
	 * @return bool
1292 533
	 */
1293 194
	public function hasIcon($size, $type = 'icon') {
1294
		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1295 509
	}
1296 509
1297
	/**
1298
	 * Get the URL for this entity's icon
1299
	 *
1300
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1301
	 * to customize the icon for an entity.
1302
	 *
1303
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1304
	 *                      or an array of parameters including 'size'
1305 533
	 * @return string The URL
1306 513
	 * @since 1.8.0
1307
	 */
1308
	public function getIconURL($params = []) {
1309 533
		return _elgg_services()->iconService->getIconURL($this, $params);
1310
	}
1311
1312
	/**
1313
	 * Save an entity.
1314
	 *
1315
	 * @return bool|int
1316
	 */
1317
	public function save() {
1318
		$guid = $this->guid;
1319
		if ($guid > 0) {
1320
			$guid = $this->update();
1321
		} else {
1322
			$guid = $this->create();
1323
			if ($guid && !_elgg_services()->hooks->getEvents()->trigger('create', $this->type, $this)) {
1324
				// plugins that return false to event don't need to override the access system
1325 509
				$ia = elgg_set_ignore_access(true);
1326
				$this->delete();
1327 509
				elgg_set_ignore_access($ia);
1328 509
				return false;
1329
			}
1330
		}
1331
1332
		if ($guid) {
1333 509
			$this->cache();
1334 509
		}
1335
1336
		return $guid;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $guid also could return the type integer which is incompatible with the return type mandated by ElggData::save() of boolean.
Loading history...
1337
	}
1338 509
1339 509
	/**
1340 509
	 * Create a new entry in the entities table.
1341 509
	 *
1342
	 * Saves the base information in the entities table for the entity.  Saving
1343 509
	 * the type-specific information is handled in the calling class method.
1344 509
	 *
1345 179
	 * @warning Entities must have an entry in both the entities table and their type table
1346 179
	 * or they will throw an exception when loaded.
1347
	 *
1348 509
	 * @return int The new entity's GUID
1349
	 * @throws InvalidParameterException If the entity's type has not been set.
1350 509
	 * @throws IOException If the new row fails to write to the DB.
1351
	 */
1352
	protected function create() {
1353
1354 509
		$type = $this->attributes['type'];
1355
		if (!in_array($type, \Elgg\Config::getEntityTypes())) {
1356
			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1357
					. implode(', ', \Elgg\Config::getEntityTypes()));
1358 509
		}
1359
1360
		$subtype = $this->attributes['subtype'];
1361 509
		if (!$subtype) {
1362 402
			throw new \InvalidParameterException("All entities must have a subtype");
1363 402
		}
1364
1365
		$owner_guid = (int) $this->attributes['owner_guid'];
1366
		$access_id = (int) $this->attributes['access_id'];
1367
		$now = $this->getCurrentTime()->getTimestamp();
1368
		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1369
1370
		$container_guid = $this->attributes['container_guid'];
1371 402
		if ($container_guid == 0) {
1372
			$container_guid = $owner_guid;
1373
			$this->attributes['container_guid'] = $container_guid;
1374
		}
1375
		$container_guid = (int) $container_guid;
1376
1377
		if ($access_id == ACCESS_DEFAULT) {
1378
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
1379 509
		}
1380 403
		
1381 403
		if ($access_id == ACCESS_FRIENDS) {
1382
			throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
1383
		}
1384
1385
		$user_guid = elgg_get_logged_in_user_guid();
1386
1387 403
		// If given an owner, verify it can be loaded
1388
		if ($owner_guid) {
1389
			$owner = $this->getOwnerEntity();
1390
			if (!$owner) {
1391
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1392
					. " owner $owner_guid could not be loaded.");
1393
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1394
			}
1395 509
1396 509
			// If different owner than logged in, verify can write to container.
1397 509
1398 509
			if ($user_guid != $owner_guid && !$owner->canWriteToContainer($user_guid, $type, $subtype)) {
1399 509
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1400 509
					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1401 509
				return false;
1402 509
			}
1403 509
		}
1404 509
1405
		// If given a container, verify it can be loaded and that the current user can write to it
1406 509
		if ($container_guid) {
1407
			$container = $this->getContainerEntity();
1408
			if (!$container) {
1409
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1410 509
					. " container $container_guid could not be loaded.");
1411 509
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1412 509
			}
1413 509
1414 509
			if (!$container->canWriteToContainer($user_guid, $type, $subtype)) {
1415 509
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1416
					. " permitted to write to container $container_guid.");
1417
				return false;
1418
			}
1419
		}
1420 509
1421
		// Create primary table row
1422
		$guid = _elgg_services()->entityTable->insertRow((object) [
1423 509
			'type' => $type,
1424 475
			'subtype' => $subtype,
1425 475
			'owner_guid' => $owner_guid,
1426
			'container_guid' => $container_guid,
1427 475
			'access_id' => $access_id,
1428
			'time_created' => $time_created,
1429 475
			'time_updated' => $now,
1430
			'last_action' => $now,
1431
		], $this->attributes);
1432
1433 475
		if (!$guid) {
1434
			throw new \IOException("Unable to save new object's base entity information!");
1435
		}
1436
1437 509
		$this->attributes['subtype'] = $subtype;
1438 17
		$this->attributes['guid'] = (int) $guid;
1439 17
		$this->attributes['time_created'] = (int) $time_created;
1440
		$this->attributes['time_updated'] = (int) $now;
1441
		$this->attributes['last_action'] = (int) $now;
1442 17
		$this->attributes['container_guid'] = (int) $container_guid;
1443
1444
		// We are writing this new entity to cache to make sure subsequent calls
1445
		// to get_entity() load the entity from cache and not from the DB. This
1446 509
		// MUST come before the metadata and annotation writes below!
1447 44
		$this->cache();
1448 44
1449
		// Save any unsaved metadata
1450
		if (sizeof($this->temp_metadata) > 0) {
0 ignored issues
show
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1450
		if (/** @scrutinizer ignore-call */ sizeof($this->temp_metadata) > 0) {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1451 44
			foreach ($this->temp_metadata as $name => $value) {
1452
				if (count($value) == 1) {
1453
					// temp metadata is always an array, but if there is only one value return just the value
1454 509
					$this->$name = $value[0];
1455
				} else {
1456
					$this->$name = $value;
1457
				}
1458
			}
1459
1460
			$this->temp_metadata = [];
1461
		}
1462
1463
		// Save any unsaved annotations.
1464 194
		if (sizeof($this->temp_annotations) > 0) {
1465
			foreach ($this->temp_annotations as $name => $value) {
1466 194
				$this->annotate($name, $value);
1467 26
			}
1468
1469
			$this->temp_annotations = [];
1470
		}
1471 172
1472
		// Save any unsaved private settings.
1473
		if (sizeof($this->temp_private_settings) > 0) {
1474
			foreach ($this->temp_private_settings as $name => $value) {
1475 172
				$this->setPrivateSetting($name, $value);
1476
			}
1477
1478 172
			$this->temp_private_settings = [];
1479 172
		}
1480 172
1481 172
		return $guid;
1482 172
	}
1483 172
1484
	/**
1485 172
	 * Update the entity in the database.
1486
	 *
1487
	 * @return bool Whether the update was successful.
1488
	 *
1489 172
	 * @throws InvalidParameterException
1490
	 */
1491
	protected function update() {
1492
1493
		if (!$this->canEdit()) {
1494 172
			return false;
1495 172
		}
1496 172
1497 172
		// give old update event a chance to stop the update
1498 172
		if (!_elgg_services()->hooks->getEvents()->trigger('update', $this->type, $this)) {
1499 172
			return false;
1500 172
		}
1501
1502 172
		$this->invalidateCache();
1503
1504
		// See #6225. We copy these after the update event in case a handler changed one of them.
1505
		$guid = (int) $this->guid;
1506 172
		$owner_guid = (int) $this->owner_guid;
1507
		$access_id = (int) $this->access_id;
1508 172
		$container_guid = (int) $this->container_guid;
1509
		$time_created = (int) $this->time_created;
1510
		$time = $this->getCurrentTime()->getTimestamp();
1511 172
1512 5
		if ($access_id == ACCESS_DEFAULT) {
1513
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
1514
		}
1515 172
	
1516
		if ($access_id == ACCESS_FRIENDS) {
1517 172
			throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
1518
		}
1519
1520 172
		// Update primary table
1521
		$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1522
			'owner_guid' => $owner_guid,
1523
			'container_guid' => $container_guid,
1524
			'access_id' => $access_id,
1525
			'time_created' => $time_created,
1526
			'time_updated' => $time,
1527
			'guid' => $guid,
1528
		]);
1529
		if ($ret === false) {
1530 5313
			return false;
1531 5313
		}
1532
1533 5313
		$this->attributes['time_updated'] = $time;
1534
1535
		elgg_trigger_after_event('update', $this->type, $this);
0 ignored issues
show
Bug introduced by
$this of type ElggEntity is incompatible with the type string expected by parameter $object of elgg_trigger_after_event(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1535
		elgg_trigger_after_event('update', $this->type, /** @scrutinizer ignore-type */ $this);
Loading history...
1536
1537
		// TODO(evan): Move this to \ElggObject?
1538 5313
		if ($this instanceof \ElggObject && isset($this->orig_attributes['access_id'])) {
1539 5313
			update_river_access_by_object($guid, $access_id);
1540 24
		}
1541 24
1542 24
		$this->orig_attributes = [];
1543
1544
		$this->cache();
1545 5313
1546 5313
		// Handle cases where there was no error BUT no rows were updated!
1547
		return true;
1548
	}
1549
1550 5313
	/**
1551
	 * Loads attributes from the entities table into the object.
1552 5313
	 *
1553
	 * @param stdClass $row Object of properties from database row(s)
1554 5313
	 *
1555
	 * @return bool
1556
	 */
1557
	protected function load(stdClass $row) {
1558
		$attributes = array_merge($this->attributes, (array) $row);
1559
1560
		if (array_diff(self::$primary_attr_names, array_keys($attributes)) !== []) {
1561
			// Some primary attributes are missing
1562
			return false;
1563
		}
1564
1565
		foreach ($attributes as $name => $value) {
1566
			if (!in_array($name, self::$primary_attr_names)) {
1567
				$this->setVolatileData("select:$name", $value);
1568
				unset($attributes[$name]);
1569
				continue;
1570
			}
1571
1572
			if (in_array($name, self::$integer_attr_names)) {
1573
				$attributes[$name] = (int) $value;
1574
			}
1575
		}
1576
1577
		$this->attributes = $attributes;
1578
1579
		$this->cache();
1580
1581
		return true;
1582
	}
1583
1584
	/**
1585
	 * Load new data from database into existing entity. Overwrites data but
1586
	 * does not change values not included in the latest data.
1587
	 *
1588
	 * @internal This is used when the same entity is selected twice during a
1589
	 * request in case different select clauses were used to load different data
1590
	 * into volatile data.
1591
	 *
1592
	 * @param stdClass $row DB row with new entity data
1593
	 * @return bool
1594
	 * @access private
1595 5
	 */
1596 5
	public function refresh(stdClass $row) {
1597
		if ($row instanceof stdClass) {
1598
			return $this->load($row);
1599
		}
1600 5
		return false;
1601
	}
1602
1603
	/**
1604 5
	 * Disable this entity.
1605
	 *
1606
	 * Disabled entities are not returned by getter functions.
1607
	 * To enable an entity, use {@link \ElggEntity::enable()}.
1608 5
	 *
1609
	 * Recursively disabling an entity will disable all entities
1610 1
	 * owned or contained by the parent entity.
1611 1
	 *
1612
	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1613 4
	 *
1614
	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1615
	 *
1616 5
	 * @param string $reason    Optional reason
1617
	 * @param bool   $recursive Recursively disable all contained entities?
1618
	 *
1619
	 * @return bool
1620 5
	 * @see \ElggEntity::enable()
1621
	 */
1622 5
	public function disable($reason = "", $recursive = true) {
1623
		if (!$this->guid) {
1624 5
			return false;
1625
		}
1626 5
1627 5
		if (!_elgg_services()->hooks->getEvents()->trigger('disable', $this->type, $this)) {
1628
			return false;
1629 5
		}
1630
1631
		if (!$this->canEdit()) {
1632 5
			return false;
1633 5
		}
1634
1635
		if ($this instanceof ElggUser && !$this->isBanned()) {
1636
			// temporarily ban to prevent using the site during disable
1637
			$this->ban();
1638 5
			$unban_after = true;
1639 5
		} else {
1640 5
			$unban_after = false;
1641
		}
1642 5
1643 5
		if ($reason) {
1644
			$this->disable_reason = $reason;
1 ignored issue
show
Bug Best Practice introduced by
The property disable_reason does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1645 5
		}
1646
1647 2
		$dbprefix = _elgg_config()->dbprefix;
1648
1649
		$guid = (int) $this->guid;
1650 2
1651 5
		if ($recursive) {
1652
			// Only disable enabled subentities
1653
			$hidden = access_get_show_hidden_status();
1654
			access_show_hidden_entities(false);
1655 5
1656 5
			$ia = elgg_set_ignore_access(true);
1657
1658
			$base_options = [
1659 5
				'wheres' => [
1660
					"e.guid != $guid",
1661
				],
1662 5
				'limit' => false,
1663
			];
1664
1665
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1666
				$options = $base_options;
1667 5
				$options[$db_column] = $guid;
1668
1669 5
				$subentities = new \ElggBatch('elgg_get_entities', $options);
1670
				$subentities->setIncrementOffset(false);
1671 5
1672 1
				foreach ($subentities as $subentity) {
1673
					/* @var $subentity \ElggEntity */
1674
					if (!$subentity->isEnabled()) {
1675 5
						continue;
1676 5
					}
1677
					add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1678 5
					$subentity->disable($reason);
1679 5
				}
1680
			}
1681
1682 5
			access_show_hidden_entities($hidden);
1683
			elgg_set_ignore_access($ia);
1684
		}
1685
1686
		$this->disableAnnotations();
1687
1688
		$sql = "
1689
			UPDATE {$dbprefix}entities
1690
			SET enabled = 'no'
1691
			WHERE guid = :guid
1692
		";
1693
		$params = [
1694
			':guid' => $guid,
1695 3
		];
1696 3
		$disabled = $this->getDatabase()->updateData($sql, false, $params);
1697 3
1698
		if ($unban_after) {
1699
			$this->unban();
1 ignored issue
show
Bug introduced by
The method unban() does not exist on ElggEntity. It seems like you code against a sub-type of ElggEntity such as ElggUser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1699
			$this->/** @scrutinizer ignore-call */ 
1700
          unban();
Loading history...
1700
		}
1701 3
1702
		if ($disabled) {
1703
			$this->invalidateCache();
1704
1705 3
			$this->attributes['enabled'] = 'no';
1706
			_elgg_services()->hooks->getEvents()->trigger('disable:after', $this->type, $this);
1707
		}
1708
1709
		return (bool) $disabled;
1710 3
	}
1711 3
1712
	/**
1713 3
	 * Enable the entity
1714 3
	 *
1715 3
	 * @warning Disabled entities can't be loaded unless
1716
	 * {@link access_show_hidden_entities(true)} has been called.
1717 3
	 *
1718
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
1719
	 * @see access_show_hiden_entities()
1720 3
	 * @return bool
1721 3
	 */
1722
	public function enable($recursive = true) {
1723 3
		$guid = (int) $this->guid;
1724 3
		if (!$guid) {
1725 3
			return false;
1726 3
		}
1727
1728 3
		if (!_elgg_services()->hooks->getEvents()->trigger('enable', $this->type, $this)) {
1729
			return false;
1730
		}
1731 3
1732 1
		if (!$this->canEdit()) {
1733 1
			return false;
1734
		}
1735
1736
		// Override access only visible entities
1737 3
		$old_access_status = access_get_show_hidden_status();
1738
		access_show_hidden_entities(true);
1739 3
1740 3
		$db = $this->getDatabase();
1741 3
		$result = $db->updateData("
1742
			UPDATE {$db->prefix}entities
1743
			SET enabled = 'yes'
1744 3
			WHERE guid = $guid
1745
		");
1746
1747
		$this->deleteMetadata('disable_reason');
1748
		$this->enableAnnotations();
1749
1750
		if ($recursive) {
1751
			$disabled_with_it = elgg_get_entities([
1752 8
				'relationship' => 'disabled_with',
1753 8
				'relationship_guid' => $guid,
1754
				'inverse_relationship' => true,
1755
				'limit' => 0,
1756
			]);
1757
1758
			foreach ($disabled_with_it as $e) {
1759
				$e->enable();
1760
				remove_entity_relationship($e->guid, 'disabled_with', $guid);
1761
			}
1762
		}
1763
1764
		access_show_hidden_entities($old_access_status);
1765
1766
		if ($result) {
1767
			$this->attributes['enabled'] = 'yes';
1768
			_elgg_services()->hooks->getEvents()->trigger('enable:after', $this->type, $this);
1769
		}
1770
1771
		return $result;
1772
	}
1773 459
1774
	/**
1775
	 * Is this entity enabled?
1776
	 *
1777 459
	 * @return boolean Whether this entity is enabled.
1778 247
	 */
1779
	public function isEnabled() {
1780
		return $this->enabled == 'yes';
1781
	}
1782 215
1783
	/**
1784
	 * Deletes the entity.
1785
	 *
1786
	 * Removes the entity and its metadata, annotations, relationships,
1787
	 * river entries, and private data.
1788
	 *
1789
	 * Optionally can remove entities contained and owned by this entity.
1790
	 *
1791
	 * @warning If deleting recursively, this bypasses ownership of items contained by
1792 5
	 * the entity.  That means that if the container_guid = $this->guid, the item will
1793 5
	 * be deleted regardless of who owns it.
1794 5
	 *
1795 5
	 * @param bool $recursive If true (default) then all entities which are
1796 5
	 *                        owned or contained by $this will also be deleted.
1797
	 *
1798
	 * @return bool
1799
	 */
1800
	public function delete($recursive = true) {
1801
		// first check if we can delete this entity
1802
		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
1803
		// which could potentially remove some content if the user didn't have access
1804
		if (!$this->canDelete()) {
1805 5
			return false;
1806 5
		}
1807 5
1808 5
		try {
1809 5
			return _elgg_services()->entityTable->delete($this, $recursive);
1810 5
		} catch (DatabaseException $ex) {
1811 5
			elgg_log($ex->getMessage(), 'ERROR');
1812 5
			return false;
1813 5
		}
1814 5
	}
1815 5
1816
	/**
1817
	 * {@inheritdoc}
1818
	 */
1819
	public function toObject() {
1820
		$object = $this->prepareObject(new stdClass());
1821
		$params = ['entity' => $this];
1822
		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1823
		return $object;
1824
	}
1825
1826
	/**
1827
	 * Prepare an object copy for toObject()
1828
	 *
1829
	 * @param stdClass $object Object representation of the entity
1830
	 * @return stdClass
1831
	 */
1832
	protected function prepareObject($object) {
1833
		$object->guid = $this->guid;
1834
		$object->type = $this->getType();
1835
		$object->subtype = $this->getSubtype();
1836
		$object->owner_guid = $this->getOwnerGUID();
1837
		$object->container_guid = $this->getContainerGUID();
1838
		$object->time_created = date('c', $this->getTimeCreated());
1839
		$object->time_updated = date('c', $this->getTimeUpdated());
1840
		$object->url = $this->getURL();
1841
		$object->read_access = (int) $this->access_id;
1842
		return $object;
1843
	}
1844
1845
	/*
1846
	 * LOCATABLE INTERFACE
1847
	 */
1848
1849
	/**
1850
	 * Gets the 'location' metadata for the entity
1851
	 *
1852
	 * @return string The location
1853
	 */
1854
	public function getLocation() {
1855
		return $this->location;
1856
	}
1857
1858
	/**
1859
	 * Sets the 'location' metadata for the entity
1860
	 *
1861
	 * @param string $location String representation of the location
1862 3
	 *
1863 3
	 * @return void
1864
	 */
1865
	public function setLocation($location) {
1866
		$this->location = $location;
1867
	}
1868
1869
	/**
1870
	 * Set latitude and longitude metadata tags for a given entity.
1871
	 *
1872 3
	 * @param float $lat  Latitude
1873 3
	 * @param float $long Longitude
1874
	 *
1875
	 * @return void
1876
	 * @todo Unimplemented
1877
	 */
1878
	public function setLatLong($lat, $long) {
1879
		$this->{"geo:lat"} = $lat;
1880
		$this->{"geo:long"} = $long;
1881
	}
1882
1883
	/**
1884
	 * Return the entity's latitude.
1885
	 *
1886 350
	 * @return float
1887 350
	 * @todo Unimplemented
1888
	 */
1889
	public function getLatitude() {
1890
		return (float) $this->{"geo:lat"};
1891
	}
1892
1893
	/**
1894
	 * Return the entity's longitude
1895
	 *
1896
	 * @return float
1897
	 * @todo Unimplemented
1898
	 */
1899
	public function getLongitude() {
1900
		return (float) $this->{"geo:long"};
1901
	}
1902
1903
	/*
1904
	 * SYSTEM LOG INTERFACE
1905
	 */
1906
1907
	/**
1908
	 * Return an identification for the object for storage in the system log.
1909
	 * This id must be an integer.
1910
	 *
1911
	 * @return int
1912
	 */
1913
	public function getSystemLogID() {
1914
		return $this->getGUID();
1915
	}
1916
1917
	/**
1918
	 * For a given ID, return the object associated with it.
1919
	 * This is used by the system log. It can be called on any Loggable object.
1920
	 *
1921
	 * @param int $id GUID.
1922
	 * @return int GUID
1923
	 */
1924
	public function getObjectFromID($id) {
1925
		return get_entity($id);
0 ignored issues
show
Bug Best Practice introduced by
The expression return get_entity($id) returns the type ElggEntity which is incompatible with the documented return type integer.
Loading history...
1926
	}
1927
1928
	/**
1929
	 * Returns tags for this entity.
1930
	 *
1931
	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
1932
	 *
1933
	 * @param array $tag_names Optionally restrict by tag metadata names.
1934
	 *
1935
	 * @return array
1936
	 */
1937
	public function getTags($tag_names = null) {
1938
		if ($tag_names && !is_array($tag_names)) {
1939
			$tag_names = [$tag_names];
1940
		}
1941
1942
		$valid_tags = elgg_get_registered_tag_metadata_names();
1943 214
		$entity_tags = [];
1944
1945 214
		foreach ($valid_tags as $tag_name) {
1946
			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
1947
				continue;
1948
			}
1949 214
1950 191
			if ($tags = $this->$tag_name) {
1951
				// if a single tag, metadata returns a string.
1952
				// if multiple tags, metadata returns an array.
1953 70
				if (is_array($tags)) {
1954
					$entity_tags = array_merge($entity_tags, $tags);
1955 70
				} else {
1956 70
					$entity_tags[] = $tags;
1957 69
				}
1958
			}
1959
		}
1960 4
1961 4
		return $entity_tags;
1962 4
	}
1963
1964
	/**
1965 4
	 * Remove the membership of all access collections for this entity (if the entity is a user)
1966
	 *
1967
	 * @return bool
1968
	 * @since 1.11
1969
	 */
1970
	public function deleteAccessCollectionMemberships() {
1971
1972
		if (!$this->guid) {
1973
			return false;
1974 214
		}
1975
1976 214
		if ($this->type !== 'user') {
1977
			return true;
1978
		}
1979
1980 214
		$ac = _elgg_services()->accessCollections;
1981 214
1982 208
		$collections = $ac->getCollectionsByMember($this->guid);
1983
		if (empty($collections)) {
1984
			return true;
1985 45
		}
1986 45
1987 45
		$result = true;
1988
		foreach ($collections as $collection) {
1989
			$result = $result & $ac->removeUser($this->guid, $collection->id);
1990 45
		}
1991
1992
		return $result;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $result also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
1993
	}
1994
1995
	/**
1996
	 * Remove all access collections owned by this entity
1997
	 *
1998
	 * @return bool
1999
	 * @since 1.11
2000
	 */
2001
	public function deleteOwnedAccessCollections() {
2002
2003 122
		if (!$this->guid) {
2004 122
			return false;
2005 122
		}
2006 122
2007 122
		$collections = $this->getOwnedAccessCollections();
2008
		if (empty($collections)) {
2009 122
			return true;
2010
		}
2011
2012
		$result = true;
2013
		foreach ($collections as $collection) {
2014
			$result = $result & $collection->delete();
2015
		}
2016
2017
		return $result;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $result also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2018 2
	}
2019 2
2020 2
	/**
2021 2
	 * Update the last_action column in the entities table.
2022
	 *
2023 2
	 * @warning This is different to time_updated.  Time_updated is automatically set,
2024
	 * while last_action is only set when explicitly called.
2025
	 *
2026
	 * @param int $posted Timestamp of last action
2027
	 * @return int|false
2028
	 * @access private
2029
	 */
2030
	public function updateLastAction($posted = null) {
2031 1
		$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2032 1
		if ($posted) {
2033 1
			$this->attributes['last_action'] = $posted;
2034
			$this->cache();
2035
		}
2036
		return $posted;
2037
	}
2038
2039
	/**
2040
	 * Disable runtime caching for entity
2041 5308
	 *
2042 5308
	 * @return void
2043 827
	 * @internal
2044
	 */
2045 5118
	public function disableCaching() {
2046
		$this->_is_cacheable = false;
2047
		if ($this->guid) {
2048
			_elgg_services()->entityCache->delete($this->guid);
2049
		}
2050
	}
2051
2052
	/**
2053
	 * Enable runtime caching for entity
2054
	 *
2055
	 * @return void
2056 5313
	 * @internal
2057 5313
	 */
2058 828
	public function enableCaching() {
2059
		$this->_is_cacheable = true;
2060
	}
2061 5125
2062
	/**
2063 5125
	 * Is entity cacheable in the runtime cache
2064 5125
	 *
2065
	 * @return bool
2066
	 * @internal
2067 5125
	 */
2068
	public function isCacheable() {
2069 5125
		if (_elgg_services()->session->getIgnoreAccess()) {
2070
			return false;
2071 5125
		}
2072
		return $this->_is_cacheable;
2073 5125
	}
2074
2075
	/**
2076
	 * Cache the entity in a session and persisted caches
2077
	 *
2078
	 * @param bool $persist Store in persistent cache
2079
	 *
2080
	 * @return void
2081 1068
	 * @internal
2082 1068
	 */
2083
	public function cache($persist = true) {
2084
		if (!$this->isCacheable()) {
2085
			return;
2086 1068
		}
2087
2088 1068
		_elgg_services()->entityCache->save($this);
2089
2090
		if ($persist) {
2091 1068
			$tmp = $this->volatile;
2092
2093
			// don't store volatile data
2094
			$this->volatile = [];
2095
2096 1068
			_elgg_services()->dataCache->entities->save($this->guid, $this);
0 ignored issues
show
Bug introduced by
$this of type ElggEntity is incompatible with the type string expected by parameter $data of Elgg\Cache\CompositeCache::save(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2096
			_elgg_services()->dataCache->entities->save($this->guid, /** @scrutinizer ignore-type */ $this);
Loading history...
2097 1068
2098
			$this->volatile = $tmp;
2099 1068
		}
2100
	}
2101
2102
	/**
2103
	 * Invalidate cache for entity
2104
	 *
2105
	 * @return void
2106
	 * @internal
2107
	 */
2108
	public function invalidateCache() {
2109
		if (!$this->guid) {
2110
			return;
2111
		}
2112
2113
		_elgg_services()->boot->invalidateCache();
2114
2115
		_elgg_services()->entityCache->delete($this->guid);
2116
2117
		$namespaces = [
2118
			'entities',
2119
			'metadata',
2120
			'private_settings',
2121
		];
2122
2123
		foreach ($namespaces as $namespace) {
2124
			_elgg_services()->dataCache->get($namespace)->delete($this->guid);
2125
		}
2126
	}
2127
}
2128