Passed
Push — master ( c2d8e3...289151 )
by Jeroen
06:06
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
				$delete_result = elgg_call(ELGG_IGNORE_ACCESS, function() use ($name) {
425 1062
					return elgg_delete_metadata([
426 1062
						'guid' => $this->guid,
427
						'metadata_name' => $name,
428 1062
						'limit' => false,
429
					]);
430 569
				});
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
728
		$flags = ELGG_IGNORE_ACCESS;
729
		$callback = function() use ($name) {
730 5
			return elgg_delete_annotations([
731
				'annotation_owner_guid' => $this->guid,
732
				'limit' => 0,
733
				'annotation_name' => $name,
734
			]);
735
		};
736
737
		return elgg_call($flags, $callback);
738
	}
739
740
	/**
741
	 * Disables annotations for this entity, optionally based on name.
742 3
	 *
743
	 * @param string $name An options name of annotations to disable.
744 3
	 * @return bool
745 3
	 * @since 1.8
746
	 */
747 3
	public function disableAnnotations($name = '') {
748
		$options = [
749
			'guid' => $this->guid,
750
			'limit' => 0
751 3
		];
752
		if ($name) {
753
			$options['annotation_name'] = $name;
754
		}
755
756
		return elgg_disable_annotations($options);
757
	}
758
759
	/**
760
	 * Enables annotations for this entity, optionally based on name.
761 2
	 *
762
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
763 2
	 *
764
	 * @param string $name An options name of annotations to enable.
765 2
	 * @return bool
766 2
	 * @since 1.8
767
	 */
768
	public function enableAnnotations($name = '') {
769 2
		$options = [
770
			'guid' => $this->guid,
771
			'limit' => 0
772
		];
773
		if ($name) {
774
			$options['annotation_name'] = $name;
775
		}
776
777
		return elgg_enable_annotations($options);
778
	}
779
780
	/**
781
	 * Helper function to return annotation calculation results
782
	 *
783
	 * @param string $name        The annotation name.
784
	 * @param string $calculation A valid MySQL function to run its values through
785
	 * @return mixed
786
	 */
787
	private function getAnnotationCalculation($name, $calculation) {
788
		$options = [
789
			'guid' => $this->getGUID(),
790
			'distinct' => false,
791 93
			'annotation_name' => $name,
792 93
			'annotation_calculation' => $calculation
793 93
		];
794 92
795
		return elgg_get_annotations($options);
796 93
	}
797 93
798 93
	/**
799 93
	 * Adds an annotation to an entity.
800 93
	 *
801 93
	 * @warning By default, annotations are private.
802 93
	 *
803 93
	 * @warning Annotating an unsaved entity more than once with the same name
804
	 *          will only save the last annotation.
805 17
	 *
806
	 * @todo Update temp_annotations to store an instance of ElggAnnotation and simply call ElggAnnotation::save(),
807 17
	 *       after entity is saved
808
	 *
809
	 * @param string $name       Annotation name
810
	 * @param mixed  $value      Annotation value
811
	 * @param int    $access_id  Access ID
812
	 * @param int    $owner_guid GUID of the annotation owner
813
	 * @param string $value_type The type of annotation value
814
	 *
815
	 * @return bool|int Returns int if an annotation is saved
816
	 */
817
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $value_type = "") {
818
		if ($this->guid) {
819
			if (!$owner_guid) {
820
				$owner_guid = elgg_get_logged_in_user_guid();
821 9
			}
822 9
			$annotation = new ElggAnnotation();
823 9
			$annotation->entity_guid = $this->guid;
824
			$annotation->name = $name;
825 9
			$annotation->value_type = $value_type;
826
			$annotation->value = $value;
827
			$annotation->owner_guid = $owner_guid;
828
			$annotation->access_id = $access_id;
829
			return $annotation->save();
830
		} else {
831
			$this->temp_annotations[$name] = $value;
832
		}
833
		return true;
834
	}
835
836
	/**
837
	 * Gets an array of annotations.
838
	 *
839
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
840
	 * as the options array.
841
	 *
842
	 * @param array $options Array of options for elgg_get_annotations() except guid.
843
	 *
844 2
	 * @return array
845 2
	 * @see elgg_get_annotations()
846
	 */
847
	public function getAnnotations(array $options = []) {
848
		if ($this->guid) {
849
			$options['guid'] = $this->guid;
850
851
			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...
852
		} else {
853
			$name = elgg_extract('annotation_name', $options, '');
854
855
			if (isset($this->temp_annotations[$name])) {
856
				return [$this->temp_annotations[$name]];
857
			}
858
		}
859
860
		return [];
861
	}
862
863
	/**
864
	 * Count annotations.
865
	 *
866
	 * @param string $name The type of annotation.
867
	 *
868
	 * @return int
869
	 */
870
	public function countAnnotations($name = "") {
871
		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...
872
	}
873
874
	/**
875
	 * Get the average of an integer type annotation.
876
	 *
877
	 * @param string $name Annotation name
878
	 *
879
	 * @return int
880
	 */
881
	public function getAnnotationsAvg($name) {
882
		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...
883
	}
884
885
	/**
886
	 * Get the sum of integer type annotations of a given name.
887
	 *
888
	 * @param string $name Annotation name
889
	 *
890
	 * @return int
891
	 */
892
	public function getAnnotationsSum($name) {
893
		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...
894
	}
895
896
	/**
897
	 * Get the minimum of integer type annotations of given name.
898 2
	 *
899 2
	 * @param string $name Annotation name
900 2
	 *
901
	 * @return int
902 2
	 */
903
	public function getAnnotationsMin($name) {
904
		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...
905
	}
906 2
907 2
	/**
908 2
	 * Get the maximum of integer type annotations of a given name.
909 2
	 *
910
	 * @param string $name Annotation name
911
	 *
912
	 * @return int
913
	 */
914
	public function getAnnotationsMax($name) {
915
		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...
916
	}
917
918
	/**
919
	 * Count the number of comments attached to this entity.
920
	 *
921
	 * @return int Number of comments
922
	 * @since 1.8.0
923
	 */
924
	public function countComments() {
925 218
		$params = ['entity' => $this];
926 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...
927 218
928
		if (is_int($num)) {
929
			return $num;
930
		}
931
932
		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...
933
			'type' => 'object',
934
			'subtype' => 'comment',
935
			'container_guid' => $this->getGUID(),
936
			'count' => true,
937
			'distinct' => false,
938
		]);
939
	}
940
941
	/**
942
	 * Returns the ACLs owned by the entity
943
	 *
944
	 * @param array $options additional options to get the access collections with
945
	 *
946
	 * @return \ElggAccessCollection[]
947
	 *
948
	 * @see elgg_get_access_collections()
949
	 * @since 3.0
950
	 */
951
	public function getOwnedAccessCollections($options = []) {
952
		$options['owner_guid'] = $this->guid;
953
		return _elgg_services()->accessCollections->getEntityCollections($options);
954
	}
955
	
956
	/**
957
	 * Returns the first ACL owned by the entity with a given subtype
958
	 *
959
	 * @param string $subtype subtype of the ACL
960
	 *
961
	 * @return \ElggAccessCollection|false
962
	 *
963
	 * @since 3.0
964
	 */
965
	public function getOwnedAccessCollection($subtype) {
966
		if (!is_string($subtype) || $subtype === '') {
967
			return false;
968
		}
969
		
970
		$acls = $this->getOwnedAccessCollections([
971
			'subtype' => $subtype,
972
		]);
973
		
974
		return elgg_extract(0, $acls, false);
975
	}
976
977
	/**
978
	 * Gets an array of entities with a relationship to this entity.
979
	 *
980
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
981
	 *                       for a list of options. 'relationship_guid' is set to
982
	 *                       this entity.
983
	 *
984
	 * @return array|false An array of entities or false on failure
985
	 * @see elgg_get_entities_from_relationship()
986
	 */
987
	public function getEntitiesFromRelationship(array $options = []) {
988
		$options['relationship_guid'] = $this->guid;
989
		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...
990
	}
991
992
	/**
993 399
	 * Gets the number of entities from a specific relationship type
994 399
	 *
995
	 * @param string $relationship         Relationship type (eg "friends")
996
	 * @param bool   $inverse_relationship Invert relationship
997
	 *
998
	 * @return int|false The number of entities or false on failure
999
	 */
1000
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
1001
		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...
1002
			'relationship' => $relationship,
1003
			'relationship_guid' => $this->getGUID(),
1004
			'inverse_relationship' => $inverse_relationship,
1005
			'count' => true
1006
		]);
1007
	}
1008 462
1009 462
	/**
1010
	 * Can a user edit this entity?
1011
	 *
1012
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
1013
	 *
1014
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1015
	 *
1016
	 * @return bool Whether this entity is editable by the given user.
1017
	 * @see elgg_set_ignore_access()
1018
	 */
1019
	public function canEdit($user_guid = 0) {
1020
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
1021
	}
1022
1023
	/**
1024
	 * Can a user delete this entity?
1025
	 *
1026
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
1027 338
	 *
1028 338
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1029
	 *
1030
	 * @return bool Whether this entity is deletable by the given user.
1031
	 * @since 1.11
1032
	 * @see elgg_set_ignore_access()
1033
	 */
1034
	public function canDelete($user_guid = 0) {
1035
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
1036
	}
1037
1038
	/**
1039
	 * Can a user edit metadata on this entity?
1040
	 *
1041 407
	 * If no specific metadata is passed, it returns whether the user can
1042 407
	 * edit any metadata on the entity.
1043
	 *
1044
	 * @tip Can be overridden by by registering for the permissions_check:metadata
1045
	 * plugin hook.
1046
	 *
1047
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1048
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1049
	 *
1050
	 * @return bool
1051
	 * @see elgg_set_ignore_access()
1052
	 */
1053
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1054
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1055 4
	}
1056 4
1057
	/**
1058
	 * Can a user add an entity to this container
1059
	 *
1060
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1061
	 * @param string $type      The type of entity we're looking to write
1062
	 * @param string $subtype   The subtype of the entity we're looking to write
1063
	 *
1064
	 * @return bool
1065
	 * @see elgg_set_ignore_access()
1066
	 */
1067
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1068
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1069
	}
1070
1071
	/**
1072
	 * Can a user comment on an entity?
1073 8
	 *
1074 8
	 * @tip Can be overridden by registering for the permissions_check:comment,
1075
	 * <entity type> plugin hook.
1076
	 *
1077
	 * @param int  $user_guid User guid (default is logged in user)
1078
	 * @param bool $default   Default permission
1079
	 * @return bool
1080
	 */
1081
	public function canComment($user_guid = 0, $default = null) {
1082
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1083
	}
1084
1085
	/**
1086
	 * Can a user annotate an entity?
1087
	 *
1088
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1089
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1090
	 *
1091 548
	 * @tip If you want logged out users to annotate an object, do not call
1092 548
	 * canAnnotate(). It's easier than using the plugin hook.
1093
	 *
1094
	 * @param int    $user_guid       User guid (default is logged in user)
1095
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1096
	 *
1097
	 * @return bool
1098
	 */
1099
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1100 1
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1101
	}
1102 1
1103
	/**
1104
	 * Returns the access_id.
1105
	 *
1106
	 * @return int The access ID
1107
	 */
1108
	public function getAccessID() {
1109
		return $this->access_id;
1110 566
	}
1111 566
1112
	/**
1113
	 * Returns the guid.
1114
	 *
1115
	 * @return int|null GUID
1116
	 */
1117
	public function getGUID() {
1118
		return $this->guid;
1119 95
	}
1120 95
1121
	/**
1122
	 * Returns the entity type
1123
	 *
1124
	 * @return string The entity type
1125
	 */
1126
	public function getType() {
1127
		// this is just for the PHPUnit mocking framework
1128 414
		return $this->type;
1129 414
	}
1130
1131
	/**
1132
	 * Get the entity subtype
1133
	 *
1134
	 * @return string The entity subtype
1135
	 */
1136
	public function getSubtype() {
1137
		return $this->attributes['subtype'];
1138
	}
1139 1
1140 1
	/**
1141
	 * Get the guid of the entity's owner.
1142
	 *
1143
	 * @return int The owner GUID
1144
	 */
1145
	public function getOwnerGUID() {
1146
		return (int) $this->owner_guid;
1147
	}
1148 425
1149 425
	/**
1150
	 * Gets the \ElggEntity that owns this entity.
1151
	 *
1152
	 * @return \ElggEntity The owning entity
1153
	 */
1154
	public function getOwnerEntity() {
1155
		return get_entity($this->owner_guid);
1156
	}
1157
1158 420
	/**
1159 420
	 * Set the container for this object.
1160
	 *
1161
	 * @param int $container_guid The ID of the container.
1162
	 *
1163
	 * @return bool
1164
	 */
1165
	public function setContainerGUID($container_guid) {
1166
		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...
1167 5
	}
1168 5
1169
	/**
1170
	 * Gets the container GUID for this entity.
1171
	 *
1172
	 * @return int
1173
	 */
1174
	public function getContainerGUID() {
1175
		return (int) $this->container_guid;
1176
	}
1177
1178
	/**
1179 42
	 * Get the container entity for this object.
1180 42
	 *
1181
	 * @return \ElggEntity
1182 42
	 * @since 1.8.0
1183 39
	 */
1184
	public function getContainerEntity() {
1185
		return get_entity($this->getContainerGUID());
1186 3
	}
1187
1188
	/**
1189
	 * Returns the UNIX epoch time that this entity was last updated
1190
	 *
1191
	 * @return int UNIX epoch time
1192
	 */
1193
	public function getTimeUpdated() {
1194
		return $this->time_updated;
1195
	}
1196
1197
	/**
1198
	 * Gets the URL for this entity.
1199
	 *
1200
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1201
	 * customize the url for an entity.
1202
	 *
1203
	 * @return string The URL of the entity
1204
	 */
1205
	public function getURL() {
1206
		$url = elgg_generate_entity_url($this, 'view');
1207
1208
		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this], $url);
1209
1210
		if (empty($url)) {
1211
			return '';
1212
		}
1213
1214
		return elgg_normalize_url($url);
1215
	}
1216
1217
	/**
1218
	 * Saves icons using an uploaded file as the source.
1219
	 *
1220
	 * @param string $input_name Form input name
1221
	 * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1222
	 * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1223
	 * @return bool
1224
	 */
1225
	public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1226
		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1227
	}
1228
1229
	/**
1230
	 * Saves icons using a local file as the source.
1231
	 *
1232
	 * @param string $filename The full path to the local file
1233 7
	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1234 7
	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1235
	 * @return bool
1236
	 */
1237
	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1238
		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1239
	}
1240
1241
	/**
1242
	 * Saves icons using a file located in the data store as the source.
1243 1
	 *
1244 1
	 * @param string $file   An ElggFile instance
1245
	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1246
	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1247
	 * @return bool
1248
	 */
1249
	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1250
		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1251
	}
1252
1253
	/**
1254
	 * Returns entity icon as an ElggIcon object
1255
	 * The icon file may or may not exist on filestore
1256
	 *
1257
	 * @param string $size Size of the icon
1258
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1259
	 * @return \ElggIcon
1260
	 */
1261
	public function getIcon($size, $type = 'icon') {
1262
		return _elgg_services()->iconService->getIcon($this, $size, $type);
1263
	}
1264
1265
	/**
1266 3
	 * Removes all icon files and metadata for the passed type of icon.
1267 3
	 *
1268
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1269
	 * @return bool
1270
	 */
1271
	public function deleteIcon($type = 'icon') {
1272
		return _elgg_services()->iconService->deleteIcon($this, $type);
1273
	}
1274
1275
	/**
1276
	 * Returns the timestamp of when the icon was changed.
1277
	 *
1278
	 * @param string $size The size of the icon
1279
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1280
	 *
1281 12
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1282 12
	 */
1283
	public function getIconLastChange($size, $type = 'icon') {
1284
		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1285
	}
1286
1287
	/**
1288
	 * Returns if the entity has an icon of the passed type.
1289
	 *
1290 533
	 * @param string $size The size of the icon
1291 533
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1292 533
	 * @return bool
1293 194
	 */
1294
	public function hasIcon($size, $type = 'icon') {
1295 509
		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1296 509
	}
1297
1298
	/**
1299
	 * Get the URL for this entity's icon
1300
	 *
1301
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1302
	 * to customize the icon for an entity.
1303
	 *
1304
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1305 533
	 *                      or an array of parameters including 'size'
1306 513
	 * @return string The URL
1307
	 * @since 1.8.0
1308
	 */
1309 533
	public function getIconURL($params = []) {
1310
		return _elgg_services()->iconService->getIconURL($this, $params);
1311
	}
1312
1313
	/**
1314
	 * Save an entity.
1315
	 *
1316
	 * @return bool|int
1317
	 */
1318
	public function save() {
1319
		$guid = $this->guid;
1320
		if ($guid > 0) {
1321
			$guid = $this->update();
1322
		} else {
1323
			$guid = $this->create();
1324
			if ($guid && !_elgg_services()->hooks->getEvents()->trigger('create', $this->type, $this)) {
1325 509
				// plugins that return false to event don't need to override the access system
1326
				elgg_call(ELGG_IGNORE_ACCESS, function() {
1327 509
					return $this->delete();
1328 509
				});
1329
				return false;
1330
			}
1331
		}
1332
1333 509
		if ($guid) {
1334 509
			$this->cache();
1335
		}
1336
1337
		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...
1338 509
	}
1339 509
1340 509
	/**
1341 509
	 * Create a new entry in the entities table.
1342
	 *
1343 509
	 * Saves the base information in the entities table for the entity.  Saving
1344 509
	 * the type-specific information is handled in the calling class method.
1345 179
	 *
1346 179
	 * @warning Entities must have an entry in both the entities table and their type table
1347
	 * or they will throw an exception when loaded.
1348 509
	 *
1349
	 * @return int The new entity's GUID
1350 509
	 * @throws InvalidParameterException If the entity's type has not been set.
1351
	 * @throws IOException If the new row fails to write to the DB.
1352
	 */
1353
	protected function create() {
1354 509
1355
		$type = $this->attributes['type'];
1356
		if (!in_array($type, \Elgg\Config::getEntityTypes())) {
1357
			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1358 509
					. implode(', ', \Elgg\Config::getEntityTypes()));
1359
		}
1360
1361 509
		$subtype = $this->attributes['subtype'];
1362 402
		if (!$subtype) {
1363 402
			throw new \InvalidParameterException("All entities must have a subtype");
1364
		}
1365
1366
		$owner_guid = (int) $this->attributes['owner_guid'];
1367
		$access_id = (int) $this->attributes['access_id'];
1368
		$now = $this->getCurrentTime()->getTimestamp();
1369
		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1370
1371 402
		$container_guid = $this->attributes['container_guid'];
1372
		if ($container_guid == 0) {
1373
			$container_guid = $owner_guid;
1374
			$this->attributes['container_guid'] = $container_guid;
1375
		}
1376
		$container_guid = (int) $container_guid;
1377
1378
		if ($access_id == ACCESS_DEFAULT) {
1379 509
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
1380 403
		}
1381 403
		
1382
		if ($access_id == ACCESS_FRIENDS) {
1383
			throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
1384
		}
1385
1386
		$user_guid = elgg_get_logged_in_user_guid();
1387 403
1388
		// If given an owner, verify it can be loaded
1389
		if ($owner_guid) {
1390
			$owner = $this->getOwnerEntity();
1391
			if (!$owner) {
1392
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1393
					. " owner $owner_guid could not be loaded.");
1394
				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...
1395 509
			}
1396 509
1397 509
			// If different owner than logged in, verify can write to container.
1398 509
1399 509
			if ($user_guid != $owner_guid && !$owner->canWriteToContainer($user_guid, $type, $subtype)) {
1400 509
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1401 509
					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1402 509
				return false;
1403 509
			}
1404 509
		}
1405
1406 509
		// If given a container, verify it can be loaded and that the current user can write to it
1407
		if ($container_guid) {
1408
			$container = $this->getContainerEntity();
1409
			if (!$container) {
1410 509
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1411 509
					. " container $container_guid could not be loaded.");
1412 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...
1413 509
			}
1414 509
1415 509
			if (!$container->canWriteToContainer($user_guid, $type, $subtype)) {
1416
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1417
					. " permitted to write to container $container_guid.");
1418
				return false;
1419
			}
1420 509
		}
1421
1422
		// Create primary table row
1423 509
		$guid = _elgg_services()->entityTable->insertRow((object) [
1424 475
			'type' => $type,
1425 475
			'subtype' => $subtype,
1426
			'owner_guid' => $owner_guid,
1427 475
			'container_guid' => $container_guid,
1428
			'access_id' => $access_id,
1429 475
			'time_created' => $time_created,
1430
			'time_updated' => $now,
1431
			'last_action' => $now,
1432
		], $this->attributes);
1433 475
1434
		if (!$guid) {
1435
			throw new \IOException("Unable to save new object's base entity information!");
1436
		}
1437 509
1438 17
		$this->attributes['subtype'] = $subtype;
1439 17
		$this->attributes['guid'] = (int) $guid;
1440
		$this->attributes['time_created'] = (int) $time_created;
1441
		$this->attributes['time_updated'] = (int) $now;
1442 17
		$this->attributes['last_action'] = (int) $now;
1443
		$this->attributes['container_guid'] = (int) $container_guid;
1444
1445
		// We are writing this new entity to cache to make sure subsequent calls
1446 509
		// to get_entity() load the entity from cache and not from the DB. This
1447 44
		// MUST come before the metadata and annotation writes below!
1448 44
		$this->cache();
1449
1450
		// Save any unsaved metadata
1451 44
		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

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

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

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

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