Passed
Push — master ( c6a34d...807816 )
by Steve
11:37 queued 02:36
created
engine/classes/ElggEntity.php 1 patch
Indentation   +1954 added lines, -1954 removed lines patch added patch discarded remove patch
@@ -40,2033 +40,2033 @@
 block discarded – undo
40 40
  * @property       string $location       A location of the entity
41 41
  */
42 42
 abstract class ElggEntity extends \ElggData implements
43
-	Locatable, // Geocoding interface
44
-	\Elgg\EntityIcon // Icon interface
43
+    Locatable, // Geocoding interface
44
+    \Elgg\EntityIcon // Icon interface
45 45
 {
46 46
 	
47
-	/**
48
-	 * If set, overrides the value of getURL()
49
-	 */
50
-	protected $url_override;
51
-
52
-	/**
53
-	 * Holds metadata until entity is saved.  Once the entity is saved,
54
-	 * metadata are written immediately to the database.
55
-	 */
56
-	protected $temp_metadata = [];
57
-
58
-	/**
59
-	 * Holds annotations until entity is saved.  Once the entity is saved,
60
-	 * annotations are written immediately to the database.
61
-	 */
62
-	protected $temp_annotations = [];
63
-
64
-	/**
65
-	 * Holds private settings until entity is saved. Once the entity is saved,
66
-	 * private settings are written immediately to the database.
67
-	 */
68
-	protected $temp_private_settings = [];
69
-
70
-	/**
71
-	 * Volatile data structure for this object, allows for storage of data
72
-	 * in-memory that isn't sync'd back to the metadata table.
73
-	 */
74
-	protected $volatile = [];
75
-
76
-	/**
77
-	 * Holds the original (persisted) attribute values that have been changed but not yet saved.
78
-	 */
79
-	protected $orig_attributes = [];
47
+    /**
48
+     * If set, overrides the value of getURL()
49
+     */
50
+    protected $url_override;
51
+
52
+    /**
53
+     * Holds metadata until entity is saved.  Once the entity is saved,
54
+     * metadata are written immediately to the database.
55
+     */
56
+    protected $temp_metadata = [];
57
+
58
+    /**
59
+     * Holds annotations until entity is saved.  Once the entity is saved,
60
+     * annotations are written immediately to the database.
61
+     */
62
+    protected $temp_annotations = [];
63
+
64
+    /**
65
+     * Holds private settings until entity is saved. Once the entity is saved,
66
+     * private settings are written immediately to the database.
67
+     */
68
+    protected $temp_private_settings = [];
69
+
70
+    /**
71
+     * Volatile data structure for this object, allows for storage of data
72
+     * in-memory that isn't sync'd back to the metadata table.
73
+     */
74
+    protected $volatile = [];
75
+
76
+    /**
77
+     * Holds the original (persisted) attribute values that have been changed but not yet saved.
78
+     */
79
+    protected $orig_attributes = [];
80 80
 	
81
-	/**
82
-	 * Initialize the attributes array.
83
-	 *
84
-	 * This is vital to distinguish between metadata and base parameters.
85
-	 *
86
-	 * @return void
87
-	 */
88
-	protected function initializeAttributes() {
89
-		parent::initializeAttributes();
90
-
91
-		$this->attributes['guid'] = null;
92
-		$this->attributes['type'] = null;
93
-		$this->attributes['subtype'] = null;
94
-
95
-		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
96
-		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
97
-
98
-		$this->attributes['access_id'] = ACCESS_PRIVATE;
99
-		$this->attributes['time_updated'] = null;
100
-		$this->attributes['last_action'] = null;
101
-		$this->attributes['enabled'] = "yes";
102
-	}
103
-
104
-	/**
105
-	 * Clone an entity
106
-	 *
107
-	 * Resets the guid so that the entity can be saved as a distinct entity from
108
-	 * the original. Creation time will be set when this new entity is saved.
109
-	 * The owner and container guids come from the original entity. The clone
110
-	 * method copies metadata but does not copy annotations or private settings.
111
-	 *
112
-	 * @note metadata will have its owner and access id set when the entity is saved
113
-	 * and it will be the same as that of the entity.
114
-	 *
115
-	 * @return void
116
-	 */
117
-	public function __clone() {
118
-		$orig_entity = get_entity($this->guid);
119
-		if (!$orig_entity) {
120
-			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
121
-			return;
122
-		}
123
-
124
-		$metadata_array = elgg_get_metadata([
125
-			'guid' => $this->guid,
126
-			'limit' => 0
127
-		]);
128
-
129
-		$this->attributes['guid'] = "";
130
-
131
-		$this->attributes['subtype'] = $orig_entity->getSubtype();
132
-
133
-		// copy metadata over to new entity - slightly convoluted due to
134
-		// handling of metadata arrays
135
-		if (is_array($metadata_array)) {
136
-			// create list of metadata names
137
-			$metadata_names = [];
138
-			foreach ($metadata_array as $metadata) {
139
-				$metadata_names[] = $metadata['name'];
140
-			}
141
-			// arrays are stored with multiple enties per name
142
-			$metadata_names = array_unique($metadata_names);
143
-
144
-			// move the metadata over
145
-			foreach ($metadata_names as $name) {
146
-				$this->__set($name, $orig_entity->$name);
147
-			}
148
-		}
149
-	}
150
-
151
-	/**
152
-	 * Set an attribute or metadata value for this entity
153
-	 *
154
-	 * Anything that is not an attribute is saved as metadata.
155
-	 *
156
-	 * @warning Metadata set this way will inherit the entity's owner and
157
-	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
158
-	 *
159
-	 * @param string $name  Name of the attribute or metadata
160
-	 * @param mixed  $value The value to be set
161
-	 * @return void
162
-	 * @see \ElggEntity::setMetadata()
163
-	 */
164
-	public function __set($name, $value) {
165
-		if ($this->$name === $value) {
166
-			// quick return if value is not changing
167
-			return;
168
-		}
169
-
170
-		if (array_key_exists($name, $this->attributes)) {
171
-			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
172
-			if (is_int($this->attributes[$name])
173
-					&& is_string($value)
174
-					&& ((string) $this->attributes[$name] === $value)) {
175
-				return;
176
-			}
177
-
178
-			// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
179
-			// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
180
-			// the value to empty string, don't consider this a change.
181
-			if (in_array($name, ['title', 'name', 'description'])
182
-					&& $this->attributes[$name] === null
183
-					&& $value === "") {
184
-				return;
185
-			}
186
-
187
-			// keep original values
188
-			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
189
-				$this->orig_attributes[$name] = $this->attributes[$name];
190
-			}
191
-
192
-			// Certain properties should not be manually changed!
193
-			switch ($name) {
194
-				case 'guid':
195
-				case 'time_updated':
196
-				case 'last_action':
197
-					return;
198
-					break;
199
-				case 'access_id':
200
-				case 'owner_guid':
201
-				case 'container_guid':
202
-					if ($value !== null) {
203
-						$this->attributes[$name] = (int) $value;
204
-					} else {
205
-						$this->attributes[$name] = null;
206
-					}
207
-					break;
208
-				default:
209
-					$this->attributes[$name] = $value;
210
-					break;
211
-			}
212
-			return;
213
-		}
214
-
215
-		$this->setMetadata($name, $value);
216
-	}
217
-
218
-	/**
219
-	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
220
-	 *
221
-	 * @return array
222
-	 */
223
-	public function getOriginalAttributes() {
224
-		return $this->orig_attributes;
225
-	}
226
-
227
-	/**
228
-	 * Get an attribute or metadata value
229
-	 *
230
-	 * If the name matches an attribute, the attribute is returned. If metadata
231
-	 * does not exist with that name, a null is returned.
232
-	 *
233
-	 * This only returns an array if there are multiple values for a particular
234
-	 * $name key.
235
-	 *
236
-	 * @param string $name Name of the attribute or metadata
237
-	 * @return mixed
238
-	 */
239
-	public function __get($name) {
240
-		if (array_key_exists($name, $this->attributes)) {
241
-			if ($name === 'subtype' && $this->attributes['guid']) {
242
-				_elgg_services()->logger->warn('Reading ->subtype on a persisted entity is unreliable.');
243
-			}
244
-			return $this->attributes[$name];
245
-		}
246
-
247
-		return $this->getMetadata($name);
248
-	}
249
-
250
-	/**
251
-	 * Get the entity's display name
252
-	 *
253
-	 * @return string The title or name of this entity.
254
-	 */
255
-	abstract public function getDisplayName();
256
-
257
-	/**
258
-	 * Sets the title or name of this entity.
259
-	 *
260
-	 * @param string $displayName The title or name of this entity.
261
-	 * @return void
262
-	 */
263
-	abstract public function setDisplayName($displayName);
264
-
265
-	/**
266
-	 * Return the value of a piece of metadata.
267
-	 *
268
-	 * @param string $name Name
269
-	 *
270
-	 * @return mixed The value, or null if not found.
271
-	 */
272
-	public function getMetadata($name) {
273
-		$guid = $this->getGUID();
274
-
275
-		if (!$guid) {
276
-			if (isset($this->temp_metadata[$name])) {
277
-				// md is returned as an array only if more than 1 entry
278
-				if (count($this->temp_metadata[$name]) == 1) {
279
-					return $this->temp_metadata[$name][0];
280
-				} else {
281
-					return $this->temp_metadata[$name];
282
-				}
283
-			} else {
284
-				return null;
285
-			}
286
-		}
287
-
288
-		// upon first cache miss, just load/cache all the metadata and retry.
289
-		// if this works, the rest of this function may not be needed!
290
-		$cache = _elgg_services()->metadataCache;
291
-		if ($cache->isLoaded($guid)) {
292
-			return $cache->getSingle($guid, $name);
293
-		} else {
294
-			$cache->populateFromEntities([$guid]);
295
-			// in case ignore_access was on, we have to check again...
296
-			if ($cache->isLoaded($guid)) {
297
-				return $cache->getSingle($guid, $name);
298
-			}
299
-		}
300
-
301
-		$md = elgg_get_metadata([
302
-			'guid' => $guid,
303
-			'metadata_name' => $name,
304
-			'limit' => 0,
305
-			'distinct' => false,
306
-		]);
307
-
308
-		$value = null;
309
-
310
-		if ($md && !is_array($md)) {
311
-			$value = $md->value;
312
-		} elseif (count($md) == 1) {
313
-			$value = $md[0]->value;
314
-		} else if ($md && is_array($md)) {
315
-			$value = metadata_array_to_values($md);
316
-		}
317
-
318
-		return $value;
319
-	}
320
-
321
-	/**
322
-	 * Unset a property from metadata or attribute.
323
-	 *
324
-	 * @warning If you use this to unset an attribute, you must save the object!
325
-	 *
326
-	 * @param string $name The name of the attribute or metadata.
327
-	 *
328
-	 * @return void
329
-	 * @todo some attributes should be set to null or other default values
330
-	 */
331
-	public function __unset($name) {
332
-		if (array_key_exists($name, $this->attributes)) {
333
-			$this->attributes[$name] = "";
334
-		} else {
335
-			$this->deleteMetadata($name);
336
-		}
337
-	}
338
-
339
-	/**
340
-	 * Set metadata on this entity.
341
-	 *
342
-	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
343
-	 * Use this method if you want to explicitly set the owner or access of the metadata.
344
-	 * You cannot set the owner/access before the entity has been saved.
345
-	 *
346
-	 * @param string $name       Name of the metadata
347
-	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
348
-	 * @param string $value_type 'text', 'integer', or '' for automatic detection
349
-	 * @param bool   $multiple   Allow multiple values for a single name.
350
-	 *                           Does not support associative arrays.
351
-	 * @param int    $owner_guid GUID of entity that owns the metadata.
352
-	 *                           Default is owner of entity.
353
-	 * @param int    $access_id  Who can read the metadata relative to the owner (deprecated).
354
-	 *                           Default is the access level of the entity. Use ACCESS_PUBLIC for
355
-	 *                           compatibility with Elgg 3.0
356
-	 *
357
-	 * @return bool
358
-	 * @throws InvalidArgumentException
359
-	 */
360
-	public function setMetadata($name, $value, $value_type = '', $multiple = false, $owner_guid = 0, $access_id = null) {
361
-
362
-		// normalize value to an array that we will loop over
363
-		// remove indexes if value already an array.
364
-		if (is_array($value)) {
365
-			$value = array_values($value);
366
-		} else {
367
-			$value = [$value];
368
-		}
369
-
370
-		// saved entity. persist md to db.
371
-		if ($this->guid) {
372
-			// if overwriting, delete first.
373
-			if (!$multiple) {
374
-				$options = [
375
-					'guid' => $this->getGUID(),
376
-					'metadata_name' => $name,
377
-					'limit' => 0
378
-				];
379
-				// @todo in 1.9 make this return false if can't add metadata
380
-				// https://github.com/elgg/elgg/issues/4520
381
-				//
382
-				// need to remove access restrictions right now to delete
383
-				// because this is the expected behavior
384
-				$ia = elgg_set_ignore_access(true);
385
-				if (false === elgg_delete_metadata($options)) {
386
-					return false;
387
-				}
388
-				elgg_set_ignore_access($ia);
389
-			}
390
-
391
-			$owner_guid = $owner_guid ? (int) $owner_guid : $this->owner_guid;
392
-
393
-			if ($access_id === null) {
394
-				$access_id = $this->access_id;
395
-			} elseif ($access_id != ACCESS_PUBLIC) {
396
-				$access_id = (int) $access_id;
397
-				elgg_deprecated_notice('Setting $access_id to a value other than ACCESS_PUBLIC is deprecated. '
398
-					. 'All metadata will be public in 3.0.', '2.3');
399
-			}
400
-
401
-			// add new md
402
-			$result = true;
403
-			foreach ($value as $value_tmp) {
404
-				// at this point $value is appended because it was cleared above if needed.
405
-				$md_id = _elgg_services()->metadataTable->create($this->guid, $name, $value_tmp, $value_type,
406
-						$owner_guid, $access_id, true);
407
-				if (!$md_id) {
408
-					return false;
409
-				}
410
-			}
411
-
412
-			return $result;
413
-		} else {
414
-			// unsaved entity. store in temp array
415
-
416
-			// returning single entries instead of an array of 1 element is decided in
417
-			// getMetaData(), just like pulling from the db.
418
-
419
-			if ($owner_guid != 0 || $access_id !== null) {
420
-				$msg = "owner guid and access id cannot be used in \ElggEntity::setMetadata() until entity is saved.";
421
-				throw new \InvalidArgumentException($msg);
422
-			}
423
-
424
-			// if overwrite, delete first
425
-			if (!$multiple || !isset($this->temp_metadata[$name])) {
426
-				$this->temp_metadata[$name] = [];
427
-			}
428
-
429
-			// add new md
430
-			$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
431
-			return true;
432
-		}
433
-	}
434
-
435
-	/**
436
-	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
437
-	 * If you pass a name, only metadata matching that name will be deleted.
438
-	 *
439
-	 * @warning Calling this with no $name will clear all metadata on the entity.
440
-	 *
441
-	 * @param null|string $name The name of the metadata to remove.
442
-	 * @return bool
443
-	 * @since 1.8
444
-	 */
445
-	public function deleteMetadata($name = null) {
446
-
447
-		if (!$this->guid) {
448
-			return false;
449
-		}
450
-
451
-		$options = [
452
-			'guid' => $this->guid,
453
-			'limit' => 0
454
-		];
455
-		if ($name) {
456
-			$options['metadata_name'] = $name;
457
-		}
458
-
459
-		return elgg_delete_metadata($options);
460
-	}
461
-
462
-	/**
463
-	 * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
464
-	 * If you pass a name, only metadata matching that name will be deleted.
465
-	 *
466
-	 * @param null|string $name The name of metadata to delete.
467
-	 * @return bool
468
-	 * @since 1.8
469
-	 */
470
-	public function deleteOwnedMetadata($name = null) {
471
-		// access is turned off for this because they might
472
-		// no longer have access to an entity they created metadata on.
473
-		$ia = elgg_set_ignore_access(true);
474
-		$options = [
475
-			'metadata_owner_guid' => $this->guid,
476
-			'limit' => 0
477
-		];
478
-		if ($name) {
479
-			$options['metadata_name'] = $name;
480
-		}
481
-
482
-		$r = elgg_delete_metadata($options);
483
-		elgg_set_ignore_access($ia);
484
-		return $r;
485
-	}
486
-
487
-	/**
488
-	 * Disables metadata for this entity, optionally based on name.
489
-	 *
490
-	 * @param string $name An options name of metadata to disable.
491
-	 * @return bool
492
-	 * @since 1.8
493
-	 */
494
-	public function disableMetadata($name = '') {
495
-		$options = [
496
-			'guid' => $this->guid,
497
-			'limit' => 0
498
-		];
499
-		if ($name) {
500
-			$options['metadata_name'] = $name;
501
-		}
502
-
503
-		return elgg_disable_metadata($options);
504
-	}
505
-
506
-	/**
507
-	 * Enables metadata for this entity, optionally based on name.
508
-	 *
509
-	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
510
-	 *
511
-	 * @param string $name An options name of metadata to enable.
512
-	 * @return bool
513
-	 * @since 1.8
514
-	 */
515
-	public function enableMetadata($name = '') {
516
-		$options = [
517
-			'guid' => $this->guid,
518
-			'limit' => 0
519
-		];
520
-		if ($name) {
521
-			$options['metadata_name'] = $name;
522
-		}
523
-
524
-		return elgg_enable_metadata($options);
525
-	}
526
-
527
-	/**
528
-	 * Get a piece of volatile (non-persisted) data on this entity.
529
-	 *
530
-	 * @param string $name The name of the volatile data
531
-	 *
532
-	 * @return mixed The value or null if not found.
533
-	 */
534
-	public function getVolatileData($name) {
535
-		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
536
-	}
537
-
538
-	/**
539
-	 * Set a piece of volatile (non-persisted) data on this entity
540
-	 *
541
-	 * @param string $name  Name
542
-	 * @param mixed  $value Value
543
-	 *
544
-	 * @return void
545
-	 */
546
-	public function setVolatileData($name, $value) {
547
-		$this->volatile[$name] = $value;
548
-	}
549
-
550
-	/**
551
-	 * Cache the entity in a persisted cache
552
-	 *
553
-	 * @param ElggSharedMemoryCache $cache       Memcache or null cache
554
-	 * @param int                   $last_action Last action time
555
-	 *
556
-	 * @return void
557
-	 * @access private
558
-	 * @internal
559
-	 */
560
-	public function storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action = 0) {
561
-		$tmp = $this->volatile;
562
-
563
-		// don't store volatile data
564
-		$this->volatile = [];
565
-		if ($last_action) {
566
-			$this->attributes['last_action'] = (int) $last_action;
567
-		}
568
-		$cache->save($this->guid, $this);
569
-
570
-		$this->volatile = $tmp;
571
-	}
572
-
573
-	/**
574
-	 * Remove all relationships to and from this entity.
575
-	 * If you pass a relationship name, only relationships matching that name
576
-	 * will be deleted.
577
-	 *
578
-	 * @warning Calling this with no $relationship will clear all relationships
579
-	 * for this entity.
580
-	 *
581
-	 * @param null|string $relationship The name of the relationship to remove.
582
-	 * @return bool
583
-	 * @see \ElggEntity::addRelationship()
584
-	 * @see \ElggEntity::removeRelationship()
585
-	 */
586
-	public function deleteRelationships($relationship = null) {
587
-		$relationship = (string) $relationship;
588
-		$result = remove_entity_relationships($this->getGUID(), $relationship);
589
-		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
590
-	}
591
-
592
-	/**
593
-	 * Add a relationship between this an another entity.
594
-	 *
595
-	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
596
-	 *
597
-	 * @param int    $guid_two     GUID of the target entity of the relationship.
598
-	 * @param string $relationship The type of relationship.
599
-	 *
600
-	 * @return bool
601
-	 * @see \ElggEntity::removeRelationship()
602
-	 * @see \ElggEntity::deleteRelationships()
603
-	 */
604
-	public function addRelationship($guid_two, $relationship) {
605
-		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
606
-	}
607
-
608
-	/**
609
-	 * Remove a relationship
610
-	 *
611
-	 * @param int    $guid_two     GUID of the target entity of the relationship.
612
-	 * @param string $relationship The type of relationship.
613
-	 *
614
-	 * @return bool
615
-	 * @see \ElggEntity::addRelationship()
616
-	 * @see \ElggEntity::deleteRelationships()
617
-	 */
618
-	public function removeRelationship($guid_two, $relationship) {
619
-		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
620
-	}
621
-
622
-	/**
623
-	 * Adds a private setting to this entity.
624
-	 *
625
-	 * Private settings are similar to metadata but will not
626
-	 * be searched and there are fewer helper functions for them.
627
-	 *
628
-	 * @param string $name  Name of private setting
629
-	 * @param mixed  $value Value of private setting
630
-	 *
631
-	 * @return bool
632
-	 */
633
-	public function setPrivateSetting($name, $value) {
634
-		if ((int) $this->guid > 0) {
635
-			return set_private_setting($this->getGUID(), $name, $value);
636
-		} else {
637
-			$this->temp_private_settings[$name] = $value;
638
-			return true;
639
-		}
640
-	}
641
-
642
-	/**
643
-	 * Returns a private setting value
644
-	 *
645
-	 * @param string $name Name of the private setting
646
-	 *
647
-	 * @return mixed Null if the setting does not exist
648
-	 */
649
-	public function getPrivateSetting($name) {
650
-		if ((int) ($this->guid) > 0) {
651
-			return get_private_setting($this->getGUID(), $name);
652
-		} else {
653
-			if (isset($this->temp_private_settings[$name])) {
654
-				return $this->temp_private_settings[$name];
655
-			}
656
-		}
657
-		return null;
658
-	}
659
-
660
-	/**
661
-	 * Removes private setting
662
-	 *
663
-	 * @param string $name Name of the private setting
664
-	 *
665
-	 * @return bool
666
-	 */
667
-	public function removePrivateSetting($name) {
668
-		return remove_private_setting($this->getGUID(), $name);
669
-	}
670
-
671
-	/**
672
-	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
673
-	 * If you pass a name, only annotations matching that name will be deleted.
674
-	 *
675
-	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
676
-	 *
677
-	 * @param null|string $name The annotations name to remove.
678
-	 * @return bool
679
-	 * @since 1.8
680
-	 */
681
-	public function deleteAnnotations($name = null) {
682
-		$options = [
683
-			'guid' => $this->guid,
684
-			'limit' => 0
685
-		];
686
-		if ($name) {
687
-			$options['annotation_name'] = $name;
688
-		}
689
-
690
-		return elgg_delete_annotations($options);
691
-	}
692
-
693
-	/**
694
-	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
695
-	 * If you pass a name, only annotations matching that name will be deleted.
696
-	 *
697
-	 * @param null|string $name The name of annotations to delete.
698
-	 * @return bool
699
-	 * @since 1.8
700
-	 */
701
-	public function deleteOwnedAnnotations($name = null) {
702
-		// access is turned off for this because they might
703
-		// no longer have access to an entity they created annotations on.
704
-		$ia = elgg_set_ignore_access(true);
705
-		$options = [
706
-			'annotation_owner_guid' => $this->guid,
707
-			'limit' => 0
708
-		];
709
-		if ($name) {
710
-			$options['annotation_name'] = $name;
711
-		}
712
-
713
-		$r = elgg_delete_annotations($options);
714
-		elgg_set_ignore_access($ia);
715
-		return $r;
716
-	}
717
-
718
-	/**
719
-	 * Disables annotations for this entity, optionally based on name.
720
-	 *
721
-	 * @param string $name An options name of annotations to disable.
722
-	 * @return bool
723
-	 * @since 1.8
724
-	 */
725
-	public function disableAnnotations($name = '') {
726
-		$options = [
727
-			'guid' => $this->guid,
728
-			'limit' => 0
729
-		];
730
-		if ($name) {
731
-			$options['annotation_name'] = $name;
732
-		}
733
-
734
-		return elgg_disable_annotations($options);
735
-	}
736
-
737
-	/**
738
-	 * Enables annotations for this entity, optionally based on name.
739
-	 *
740
-	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
741
-	 *
742
-	 * @param string $name An options name of annotations to enable.
743
-	 * @return bool
744
-	 * @since 1.8
745
-	 */
746
-	public function enableAnnotations($name = '') {
747
-		$options = [
748
-			'guid' => $this->guid,
749
-			'limit' => 0
750
-		];
751
-		if ($name) {
752
-			$options['annotation_name'] = $name;
753
-		}
754
-
755
-		return elgg_enable_annotations($options);
756
-	}
757
-
758
-	/**
759
-	 * Helper function to return annotation calculation results
760
-	 *
761
-	 * @param string $name        The annotation name.
762
-	 * @param string $calculation A valid MySQL function to run its values through
763
-	 * @return mixed
764
-	 */
765
-	private function getAnnotationCalculation($name, $calculation) {
766
-		$options = [
767
-			'guid' => $this->getGUID(),
768
-			'distinct' => false,
769
-			'annotation_name' => $name,
770
-			'annotation_calculation' => $calculation
771
-		];
772
-
773
-		return elgg_get_annotations($options);
774
-	}
775
-
776
-	/**
777
-	 * Adds an annotation to an entity.
778
-	 *
779
-	 * @warning By default, annotations are private.
780
-	 *
781
-	 * @warning Annotating an unsaved entity more than once with the same name
782
-	 *          will only save the last annotation.
783
-	 *
784
-	 * @param string $name       Annotation name
785
-	 * @param mixed  $value      Annotation value
786
-	 * @param int    $access_id  Access ID
787
-	 * @param int    $owner_guid GUID of the annotation owner
788
-	 * @param string $vartype    The type of annotation value
789
-	 *
790
-	 * @return bool|int Returns int if an annotation is saved
791
-	 */
792
-	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
793
-		if ((int) $this->guid > 0) {
794
-			return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
795
-		} else {
796
-			$this->temp_annotations[$name] = $value;
797
-		}
798
-		return true;
799
-	}
800
-
801
-	/**
802
-	 * Gets an array of annotations.
803
-	 *
804
-	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
805
-	 * as the options array.
806
-	 *
807
-	 * @param array $options Array of options for elgg_get_annotations() except guid.
808
-	 *
809
-	 * @return array
810
-	 * @see elgg_get_annotations()
811
-	 */
812
-	public function getAnnotations(array $options = []) {
813
-		if ($this->guid) {
814
-			$options['guid'] = $this->guid;
815
-
816
-			return elgg_get_annotations($options);
817
-		} else {
818
-			$name = elgg_extract('annotation_name', $options, '');
819
-
820
-			if (isset($this->temp_annotations[$name])) {
821
-				return [$this->temp_annotations[$name]];
822
-			}
823
-		}
824
-
825
-		return [];
826
-	}
827
-
828
-	/**
829
-	 * Count annotations.
830
-	 *
831
-	 * @param string $name The type of annotation.
832
-	 *
833
-	 * @return int
834
-	 */
835
-	public function countAnnotations($name = "") {
836
-		return $this->getAnnotationCalculation($name, 'count');
837
-	}
838
-
839
-	/**
840
-	 * Get the average of an integer type annotation.
841
-	 *
842
-	 * @param string $name Annotation name
843
-	 *
844
-	 * @return int
845
-	 */
846
-	public function getAnnotationsAvg($name) {
847
-		return $this->getAnnotationCalculation($name, 'avg');
848
-	}
849
-
850
-	/**
851
-	 * Get the sum of integer type annotations of a given name.
852
-	 *
853
-	 * @param string $name Annotation name
854
-	 *
855
-	 * @return int
856
-	 */
857
-	public function getAnnotationsSum($name) {
858
-		return $this->getAnnotationCalculation($name, 'sum');
859
-	}
860
-
861
-	/**
862
-	 * Get the minimum of integer type annotations of given name.
863
-	 *
864
-	 * @param string $name Annotation name
865
-	 *
866
-	 * @return int
867
-	 */
868
-	public function getAnnotationsMin($name) {
869
-		return $this->getAnnotationCalculation($name, 'min');
870
-	}
871
-
872
-	/**
873
-	 * Get the maximum of integer type annotations of a given name.
874
-	 *
875
-	 * @param string $name Annotation name
876
-	 *
877
-	 * @return int
878
-	 */
879
-	public function getAnnotationsMax($name) {
880
-		return $this->getAnnotationCalculation($name, 'max');
881
-	}
882
-
883
-	/**
884
-	 * Count the number of comments attached to this entity.
885
-	 *
886
-	 * @return int Number of comments
887
-	 * @since 1.8.0
888
-	 */
889
-	public function countComments() {
890
-		$params = ['entity' => $this];
891
-		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
892
-
893
-		if (is_int($num)) {
894
-			return $num;
895
-		} else {
896
-			return elgg_get_entities([
897
-				'type' => 'object',
898
-				'subtype' => 'comment',
899
-				'container_guid' => $this->getGUID(),
900
-				'count' => true,
901
-				'distinct' => false,
902
-			]);
903
-		}
904
-	}
905
-
906
-	/**
907
-	 * Gets an array of entities with a relationship to this entity.
908
-	 *
909
-	 * @param array $options Options array. See elgg_get_entities_from_relationship()
910
-	 *                       for a list of options. 'relationship_guid' is set to
911
-	 *                       this entity.
912
-	 *
913
-	 * @return array|false An array of entities or false on failure
914
-	 * @see elgg_get_entities_from_relationship()
915
-	 */
916
-	public function getEntitiesFromRelationship(array $options = []) {
917
-		$options['relationship_guid'] = $this->guid;
918
-		return elgg_get_entities_from_relationship($options);
919
-	}
920
-
921
-	/**
922
-	 * Gets the number of entities from a specific relationship type
923
-	 *
924
-	 * @param string $relationship         Relationship type (eg "friends")
925
-	 * @param bool   $inverse_relationship Invert relationship
926
-	 *
927
-	 * @return int|false The number of entities or false on failure
928
-	 */
929
-	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
930
-		return elgg_get_entities_from_relationship([
931
-			'relationship' => $relationship,
932
-			'relationship_guid' => $this->getGUID(),
933
-			'inverse_relationship' => $inverse_relationship,
934
-			'count' => true
935
-		]);
936
-	}
937
-
938
-	/**
939
-	 * Can a user edit this entity?
940
-	 *
941
-	 * @tip Can be overridden by registering for the permissions_check plugin hook.
942
-	 *
943
-	 * @param int $user_guid The user GUID, optionally (default: logged in user)
944
-	 *
945
-	 * @return bool Whether this entity is editable by the given user.
946
-	 * @see elgg_set_ignore_access()
947
-	 */
948
-	public function canEdit($user_guid = 0) {
949
-		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
950
-	}
951
-
952
-	/**
953
-	 * Can a user delete this entity?
954
-	 *
955
-	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
956
-	 *
957
-	 * @param int $user_guid The user GUID, optionally (default: logged in user)
958
-	 *
959
-	 * @return bool Whether this entity is deletable by the given user.
960
-	 * @since 1.11
961
-	 * @see elgg_set_ignore_access()
962
-	 */
963
-	public function canDelete($user_guid = 0) {
964
-		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
965
-	}
966
-
967
-	/**
968
-	 * Can a user edit metadata on this entity?
969
-	 *
970
-	 * If no specific metadata is passed, it returns whether the user can
971
-	 * edit any metadata on the entity.
972
-	 *
973
-	 * @tip Can be overridden by by registering for the permissions_check:metadata
974
-	 * plugin hook.
975
-	 *
976
-	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
977
-	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
978
-	 *
979
-	 * @return bool
980
-	 * @see elgg_set_ignore_access()
981
-	 */
982
-	public function canEditMetadata($metadata = null, $user_guid = 0) {
983
-		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
984
-	}
985
-
986
-	/**
987
-	 * Can a user add an entity to this container
988
-	 *
989
-	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
990
-	 * @param string $type      The type of entity we're looking to write
991
-	 * @param string $subtype   The subtype of the entity we're looking to write
992
-	 *
993
-	 * @return bool
994
-	 * @see elgg_set_ignore_access()
995
-	 */
996
-	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
997
-		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
998
-	}
999
-
1000
-	/**
1001
-	 * Can a user comment on an entity?
1002
-	 *
1003
-	 * @tip Can be overridden by registering for the permissions_check:comment,
1004
-	 * <entity type> plugin hook.
1005
-	 *
1006
-	 * @param int  $user_guid User guid (default is logged in user)
1007
-	 * @param bool $default   Default permission
1008
-	 * @return bool
1009
-	 */
1010
-	public function canComment($user_guid = 0, $default = null) {
1011
-		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1012
-	}
1013
-
1014
-	/**
1015
-	 * Can a user annotate an entity?
1016
-	 *
1017
-	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1018
-	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1019
-	 *
1020
-	 * @tip If you want logged out users to annotate an object, do not call
1021
-	 * canAnnotate(). It's easier than using the plugin hook.
1022
-	 *
1023
-	 * @param int    $user_guid       User guid (default is logged in user)
1024
-	 * @param string $annotation_name The name of the annotation (default is unspecified)
1025
-	 *
1026
-	 * @return bool
1027
-	 */
1028
-	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1029
-		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1030
-	}
1031
-
1032
-	/**
1033
-	 * Returns the access_id.
1034
-	 *
1035
-	 * @return int The access ID
1036
-	 */
1037
-	public function getAccessID() {
1038
-		return $this->access_id;
1039
-	}
1040
-
1041
-	/**
1042
-	 * Returns the guid.
1043
-	 *
1044
-	 * @return int|null GUID
1045
-	 */
1046
-	public function getGUID() {
1047
-		return $this->guid;
1048
-	}
1049
-
1050
-	/**
1051
-	 * Returns the entity type
1052
-	 *
1053
-	 * @return string The entity type
1054
-	 */
1055
-	public function getType() {
1056
-		return $this->type;
1057
-	}
1058
-
1059
-	/**
1060
-	 * Get the entity subtype
1061
-	 *
1062
-	 * @return string The entity subtype
1063
-	 */
1064
-	public function getSubtype() {
1065
-		// If this object hasn't been saved, then return the subtype string.
1066
-		if ($this->attributes['guid']) {
1067
-			return get_subtype_from_id($this->attributes['subtype']);
1068
-		}
1069
-		return $this->attributes['subtype'];
1070
-	}
1071
-
1072
-	/**
1073
-	 * Get the guid of the entity's owner.
1074
-	 *
1075
-	 * @return int The owner GUID
1076
-	 */
1077
-	public function getOwnerGUID() {
1078
-		return (int) $this->owner_guid;
1079
-	}
1080
-
1081
-	/**
1082
-	 * Gets the \ElggEntity that owns this entity.
1083
-	 *
1084
-	 * @return \ElggEntity The owning entity
1085
-	 */
1086
-	public function getOwnerEntity() {
1087
-		return get_entity($this->owner_guid);
1088
-	}
1089
-
1090
-	/**
1091
-	 * Set the container for this object.
1092
-	 *
1093
-	 * @param int $container_guid The ID of the container.
1094
-	 *
1095
-	 * @return bool
1096
-	 */
1097
-	public function setContainerGUID($container_guid) {
1098
-		return $this->container_guid = (int) $container_guid;
1099
-	}
1100
-
1101
-	/**
1102
-	 * Gets the container GUID for this entity.
1103
-	 *
1104
-	 * @return int
1105
-	 */
1106
-	public function getContainerGUID() {
1107
-		return (int) $this->container_guid;
1108
-	}
1109
-
1110
-	/**
1111
-	 * Get the container entity for this object.
1112
-	 *
1113
-	 * @return \ElggEntity
1114
-	 * @since 1.8.0
1115
-	 */
1116
-	public function getContainerEntity() {
1117
-		return get_entity($this->getContainerGUID());
1118
-	}
1119
-
1120
-	/**
1121
-	 * Returns the UNIX epoch time that this entity was last updated
1122
-	 *
1123
-	 * @return int UNIX epoch time
1124
-	 */
1125
-	public function getTimeUpdated() {
1126
-		return $this->time_updated;
1127
-	}
1128
-
1129
-	/**
1130
-	 * Gets the URL for this entity.
1131
-	 *
1132
-	 * Plugins can register for the 'entity:url', <type> plugin hook to
1133
-	 * customize the url for an entity.
1134
-	 *
1135
-	 * @return string The URL of the entity
1136
-	 */
1137
-	public function getURL() {
1138
-		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
81
+    /**
82
+     * Initialize the attributes array.
83
+     *
84
+     * This is vital to distinguish between metadata and base parameters.
85
+     *
86
+     * @return void
87
+     */
88
+    protected function initializeAttributes() {
89
+        parent::initializeAttributes();
90
+
91
+        $this->attributes['guid'] = null;
92
+        $this->attributes['type'] = null;
93
+        $this->attributes['subtype'] = null;
94
+
95
+        $this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
96
+        $this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
97
+
98
+        $this->attributes['access_id'] = ACCESS_PRIVATE;
99
+        $this->attributes['time_updated'] = null;
100
+        $this->attributes['last_action'] = null;
101
+        $this->attributes['enabled'] = "yes";
102
+    }
103
+
104
+    /**
105
+     * Clone an entity
106
+     *
107
+     * Resets the guid so that the entity can be saved as a distinct entity from
108
+     * the original. Creation time will be set when this new entity is saved.
109
+     * The owner and container guids come from the original entity. The clone
110
+     * method copies metadata but does not copy annotations or private settings.
111
+     *
112
+     * @note metadata will have its owner and access id set when the entity is saved
113
+     * and it will be the same as that of the entity.
114
+     *
115
+     * @return void
116
+     */
117
+    public function __clone() {
118
+        $orig_entity = get_entity($this->guid);
119
+        if (!$orig_entity) {
120
+            _elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
121
+            return;
122
+        }
123
+
124
+        $metadata_array = elgg_get_metadata([
125
+            'guid' => $this->guid,
126
+            'limit' => 0
127
+        ]);
128
+
129
+        $this->attributes['guid'] = "";
130
+
131
+        $this->attributes['subtype'] = $orig_entity->getSubtype();
132
+
133
+        // copy metadata over to new entity - slightly convoluted due to
134
+        // handling of metadata arrays
135
+        if (is_array($metadata_array)) {
136
+            // create list of metadata names
137
+            $metadata_names = [];
138
+            foreach ($metadata_array as $metadata) {
139
+                $metadata_names[] = $metadata['name'];
140
+            }
141
+            // arrays are stored with multiple enties per name
142
+            $metadata_names = array_unique($metadata_names);
143
+
144
+            // move the metadata over
145
+            foreach ($metadata_names as $name) {
146
+                $this->__set($name, $orig_entity->$name);
147
+            }
148
+        }
149
+    }
150
+
151
+    /**
152
+     * Set an attribute or metadata value for this entity
153
+     *
154
+     * Anything that is not an attribute is saved as metadata.
155
+     *
156
+     * @warning Metadata set this way will inherit the entity's owner and
157
+     * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
158
+     *
159
+     * @param string $name  Name of the attribute or metadata
160
+     * @param mixed  $value The value to be set
161
+     * @return void
162
+     * @see \ElggEntity::setMetadata()
163
+     */
164
+    public function __set($name, $value) {
165
+        if ($this->$name === $value) {
166
+            // quick return if value is not changing
167
+            return;
168
+        }
169
+
170
+        if (array_key_exists($name, $this->attributes)) {
171
+            // if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
172
+            if (is_int($this->attributes[$name])
173
+                    && is_string($value)
174
+                    && ((string) $this->attributes[$name] === $value)) {
175
+                return;
176
+            }
177
+
178
+            // Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
179
+            // will store empty strings as null in the DB. In the somewhat common case that we're re-setting
180
+            // the value to empty string, don't consider this a change.
181
+            if (in_array($name, ['title', 'name', 'description'])
182
+                    && $this->attributes[$name] === null
183
+                    && $value === "") {
184
+                return;
185
+            }
186
+
187
+            // keep original values
188
+            if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
189
+                $this->orig_attributes[$name] = $this->attributes[$name];
190
+            }
191
+
192
+            // Certain properties should not be manually changed!
193
+            switch ($name) {
194
+                case 'guid':
195
+                case 'time_updated':
196
+                case 'last_action':
197
+                    return;
198
+                    break;
199
+                case 'access_id':
200
+                case 'owner_guid':
201
+                case 'container_guid':
202
+                    if ($value !== null) {
203
+                        $this->attributes[$name] = (int) $value;
204
+                    } else {
205
+                        $this->attributes[$name] = null;
206
+                    }
207
+                    break;
208
+                default:
209
+                    $this->attributes[$name] = $value;
210
+                    break;
211
+            }
212
+            return;
213
+        }
214
+
215
+        $this->setMetadata($name, $value);
216
+    }
217
+
218
+    /**
219
+     * Get the original values of attribute(s) that have been modified since the entity was persisted.
220
+     *
221
+     * @return array
222
+     */
223
+    public function getOriginalAttributes() {
224
+        return $this->orig_attributes;
225
+    }
226
+
227
+    /**
228
+     * Get an attribute or metadata value
229
+     *
230
+     * If the name matches an attribute, the attribute is returned. If metadata
231
+     * does not exist with that name, a null is returned.
232
+     *
233
+     * This only returns an array if there are multiple values for a particular
234
+     * $name key.
235
+     *
236
+     * @param string $name Name of the attribute or metadata
237
+     * @return mixed
238
+     */
239
+    public function __get($name) {
240
+        if (array_key_exists($name, $this->attributes)) {
241
+            if ($name === 'subtype' && $this->attributes['guid']) {
242
+                _elgg_services()->logger->warn('Reading ->subtype on a persisted entity is unreliable.');
243
+            }
244
+            return $this->attributes[$name];
245
+        }
246
+
247
+        return $this->getMetadata($name);
248
+    }
249
+
250
+    /**
251
+     * Get the entity's display name
252
+     *
253
+     * @return string The title or name of this entity.
254
+     */
255
+    abstract public function getDisplayName();
256
+
257
+    /**
258
+     * Sets the title or name of this entity.
259
+     *
260
+     * @param string $displayName The title or name of this entity.
261
+     * @return void
262
+     */
263
+    abstract public function setDisplayName($displayName);
264
+
265
+    /**
266
+     * Return the value of a piece of metadata.
267
+     *
268
+     * @param string $name Name
269
+     *
270
+     * @return mixed The value, or null if not found.
271
+     */
272
+    public function getMetadata($name) {
273
+        $guid = $this->getGUID();
274
+
275
+        if (!$guid) {
276
+            if (isset($this->temp_metadata[$name])) {
277
+                // md is returned as an array only if more than 1 entry
278
+                if (count($this->temp_metadata[$name]) == 1) {
279
+                    return $this->temp_metadata[$name][0];
280
+                } else {
281
+                    return $this->temp_metadata[$name];
282
+                }
283
+            } else {
284
+                return null;
285
+            }
286
+        }
287
+
288
+        // upon first cache miss, just load/cache all the metadata and retry.
289
+        // if this works, the rest of this function may not be needed!
290
+        $cache = _elgg_services()->metadataCache;
291
+        if ($cache->isLoaded($guid)) {
292
+            return $cache->getSingle($guid, $name);
293
+        } else {
294
+            $cache->populateFromEntities([$guid]);
295
+            // in case ignore_access was on, we have to check again...
296
+            if ($cache->isLoaded($guid)) {
297
+                return $cache->getSingle($guid, $name);
298
+            }
299
+        }
300
+
301
+        $md = elgg_get_metadata([
302
+            'guid' => $guid,
303
+            'metadata_name' => $name,
304
+            'limit' => 0,
305
+            'distinct' => false,
306
+        ]);
307
+
308
+        $value = null;
309
+
310
+        if ($md && !is_array($md)) {
311
+            $value = $md->value;
312
+        } elseif (count($md) == 1) {
313
+            $value = $md[0]->value;
314
+        } else if ($md && is_array($md)) {
315
+            $value = metadata_array_to_values($md);
316
+        }
317
+
318
+        return $value;
319
+    }
320
+
321
+    /**
322
+     * Unset a property from metadata or attribute.
323
+     *
324
+     * @warning If you use this to unset an attribute, you must save the object!
325
+     *
326
+     * @param string $name The name of the attribute or metadata.
327
+     *
328
+     * @return void
329
+     * @todo some attributes should be set to null or other default values
330
+     */
331
+    public function __unset($name) {
332
+        if (array_key_exists($name, $this->attributes)) {
333
+            $this->attributes[$name] = "";
334
+        } else {
335
+            $this->deleteMetadata($name);
336
+        }
337
+    }
338
+
339
+    /**
340
+     * Set metadata on this entity.
341
+     *
342
+     * Plugin developers usually want to use the magic set method ($entity->name = 'value').
343
+     * Use this method if you want to explicitly set the owner or access of the metadata.
344
+     * You cannot set the owner/access before the entity has been saved.
345
+     *
346
+     * @param string $name       Name of the metadata
347
+     * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
348
+     * @param string $value_type 'text', 'integer', or '' for automatic detection
349
+     * @param bool   $multiple   Allow multiple values for a single name.
350
+     *                           Does not support associative arrays.
351
+     * @param int    $owner_guid GUID of entity that owns the metadata.
352
+     *                           Default is owner of entity.
353
+     * @param int    $access_id  Who can read the metadata relative to the owner (deprecated).
354
+     *                           Default is the access level of the entity. Use ACCESS_PUBLIC for
355
+     *                           compatibility with Elgg 3.0
356
+     *
357
+     * @return bool
358
+     * @throws InvalidArgumentException
359
+     */
360
+    public function setMetadata($name, $value, $value_type = '', $multiple = false, $owner_guid = 0, $access_id = null) {
361
+
362
+        // normalize value to an array that we will loop over
363
+        // remove indexes if value already an array.
364
+        if (is_array($value)) {
365
+            $value = array_values($value);
366
+        } else {
367
+            $value = [$value];
368
+        }
369
+
370
+        // saved entity. persist md to db.
371
+        if ($this->guid) {
372
+            // if overwriting, delete first.
373
+            if (!$multiple) {
374
+                $options = [
375
+                    'guid' => $this->getGUID(),
376
+                    'metadata_name' => $name,
377
+                    'limit' => 0
378
+                ];
379
+                // @todo in 1.9 make this return false if can't add metadata
380
+                // https://github.com/elgg/elgg/issues/4520
381
+                //
382
+                // need to remove access restrictions right now to delete
383
+                // because this is the expected behavior
384
+                $ia = elgg_set_ignore_access(true);
385
+                if (false === elgg_delete_metadata($options)) {
386
+                    return false;
387
+                }
388
+                elgg_set_ignore_access($ia);
389
+            }
390
+
391
+            $owner_guid = $owner_guid ? (int) $owner_guid : $this->owner_guid;
392
+
393
+            if ($access_id === null) {
394
+                $access_id = $this->access_id;
395
+            } elseif ($access_id != ACCESS_PUBLIC) {
396
+                $access_id = (int) $access_id;
397
+                elgg_deprecated_notice('Setting $access_id to a value other than ACCESS_PUBLIC is deprecated. '
398
+                    . 'All metadata will be public in 3.0.', '2.3');
399
+            }
400
+
401
+            // add new md
402
+            $result = true;
403
+            foreach ($value as $value_tmp) {
404
+                // at this point $value is appended because it was cleared above if needed.
405
+                $md_id = _elgg_services()->metadataTable->create($this->guid, $name, $value_tmp, $value_type,
406
+                        $owner_guid, $access_id, true);
407
+                if (!$md_id) {
408
+                    return false;
409
+                }
410
+            }
411
+
412
+            return $result;
413
+        } else {
414
+            // unsaved entity. store in temp array
415
+
416
+            // returning single entries instead of an array of 1 element is decided in
417
+            // getMetaData(), just like pulling from the db.
418
+
419
+            if ($owner_guid != 0 || $access_id !== null) {
420
+                $msg = "owner guid and access id cannot be used in \ElggEntity::setMetadata() until entity is saved.";
421
+                throw new \InvalidArgumentException($msg);
422
+            }
423
+
424
+            // if overwrite, delete first
425
+            if (!$multiple || !isset($this->temp_metadata[$name])) {
426
+                $this->temp_metadata[$name] = [];
427
+            }
428
+
429
+            // add new md
430
+            $this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
431
+            return true;
432
+        }
433
+    }
434
+
435
+    /**
436
+     * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
437
+     * If you pass a name, only metadata matching that name will be deleted.
438
+     *
439
+     * @warning Calling this with no $name will clear all metadata on the entity.
440
+     *
441
+     * @param null|string $name The name of the metadata to remove.
442
+     * @return bool
443
+     * @since 1.8
444
+     */
445
+    public function deleteMetadata($name = null) {
446
+
447
+        if (!$this->guid) {
448
+            return false;
449
+        }
450
+
451
+        $options = [
452
+            'guid' => $this->guid,
453
+            'limit' => 0
454
+        ];
455
+        if ($name) {
456
+            $options['metadata_name'] = $name;
457
+        }
458
+
459
+        return elgg_delete_metadata($options);
460
+    }
461
+
462
+    /**
463
+     * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
464
+     * If you pass a name, only metadata matching that name will be deleted.
465
+     *
466
+     * @param null|string $name The name of metadata to delete.
467
+     * @return bool
468
+     * @since 1.8
469
+     */
470
+    public function deleteOwnedMetadata($name = null) {
471
+        // access is turned off for this because they might
472
+        // no longer have access to an entity they created metadata on.
473
+        $ia = elgg_set_ignore_access(true);
474
+        $options = [
475
+            'metadata_owner_guid' => $this->guid,
476
+            'limit' => 0
477
+        ];
478
+        if ($name) {
479
+            $options['metadata_name'] = $name;
480
+        }
481
+
482
+        $r = elgg_delete_metadata($options);
483
+        elgg_set_ignore_access($ia);
484
+        return $r;
485
+    }
486
+
487
+    /**
488
+     * Disables metadata for this entity, optionally based on name.
489
+     *
490
+     * @param string $name An options name of metadata to disable.
491
+     * @return bool
492
+     * @since 1.8
493
+     */
494
+    public function disableMetadata($name = '') {
495
+        $options = [
496
+            'guid' => $this->guid,
497
+            'limit' => 0
498
+        ];
499
+        if ($name) {
500
+            $options['metadata_name'] = $name;
501
+        }
502
+
503
+        return elgg_disable_metadata($options);
504
+    }
505
+
506
+    /**
507
+     * Enables metadata for this entity, optionally based on name.
508
+     *
509
+     * @warning Before calling this, you must use {@link access_show_hidden_entities()}
510
+     *
511
+     * @param string $name An options name of metadata to enable.
512
+     * @return bool
513
+     * @since 1.8
514
+     */
515
+    public function enableMetadata($name = '') {
516
+        $options = [
517
+            'guid' => $this->guid,
518
+            'limit' => 0
519
+        ];
520
+        if ($name) {
521
+            $options['metadata_name'] = $name;
522
+        }
523
+
524
+        return elgg_enable_metadata($options);
525
+    }
526
+
527
+    /**
528
+     * Get a piece of volatile (non-persisted) data on this entity.
529
+     *
530
+     * @param string $name The name of the volatile data
531
+     *
532
+     * @return mixed The value or null if not found.
533
+     */
534
+    public function getVolatileData($name) {
535
+        return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
536
+    }
537
+
538
+    /**
539
+     * Set a piece of volatile (non-persisted) data on this entity
540
+     *
541
+     * @param string $name  Name
542
+     * @param mixed  $value Value
543
+     *
544
+     * @return void
545
+     */
546
+    public function setVolatileData($name, $value) {
547
+        $this->volatile[$name] = $value;
548
+    }
549
+
550
+    /**
551
+     * Cache the entity in a persisted cache
552
+     *
553
+     * @param ElggSharedMemoryCache $cache       Memcache or null cache
554
+     * @param int                   $last_action Last action time
555
+     *
556
+     * @return void
557
+     * @access private
558
+     * @internal
559
+     */
560
+    public function storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action = 0) {
561
+        $tmp = $this->volatile;
562
+
563
+        // don't store volatile data
564
+        $this->volatile = [];
565
+        if ($last_action) {
566
+            $this->attributes['last_action'] = (int) $last_action;
567
+        }
568
+        $cache->save($this->guid, $this);
569
+
570
+        $this->volatile = $tmp;
571
+    }
572
+
573
+    /**
574
+     * Remove all relationships to and from this entity.
575
+     * If you pass a relationship name, only relationships matching that name
576
+     * will be deleted.
577
+     *
578
+     * @warning Calling this with no $relationship will clear all relationships
579
+     * for this entity.
580
+     *
581
+     * @param null|string $relationship The name of the relationship to remove.
582
+     * @return bool
583
+     * @see \ElggEntity::addRelationship()
584
+     * @see \ElggEntity::removeRelationship()
585
+     */
586
+    public function deleteRelationships($relationship = null) {
587
+        $relationship = (string) $relationship;
588
+        $result = remove_entity_relationships($this->getGUID(), $relationship);
589
+        return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
590
+    }
591
+
592
+    /**
593
+     * Add a relationship between this an another entity.
594
+     *
595
+     * @tip Read the relationship like "This entity is a $relationship of $guid_two."
596
+     *
597
+     * @param int    $guid_two     GUID of the target entity of the relationship.
598
+     * @param string $relationship The type of relationship.
599
+     *
600
+     * @return bool
601
+     * @see \ElggEntity::removeRelationship()
602
+     * @see \ElggEntity::deleteRelationships()
603
+     */
604
+    public function addRelationship($guid_two, $relationship) {
605
+        return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
606
+    }
607
+
608
+    /**
609
+     * Remove a relationship
610
+     *
611
+     * @param int    $guid_two     GUID of the target entity of the relationship.
612
+     * @param string $relationship The type of relationship.
613
+     *
614
+     * @return bool
615
+     * @see \ElggEntity::addRelationship()
616
+     * @see \ElggEntity::deleteRelationships()
617
+     */
618
+    public function removeRelationship($guid_two, $relationship) {
619
+        return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
620
+    }
621
+
622
+    /**
623
+     * Adds a private setting to this entity.
624
+     *
625
+     * Private settings are similar to metadata but will not
626
+     * be searched and there are fewer helper functions for them.
627
+     *
628
+     * @param string $name  Name of private setting
629
+     * @param mixed  $value Value of private setting
630
+     *
631
+     * @return bool
632
+     */
633
+    public function setPrivateSetting($name, $value) {
634
+        if ((int) $this->guid > 0) {
635
+            return set_private_setting($this->getGUID(), $name, $value);
636
+        } else {
637
+            $this->temp_private_settings[$name] = $value;
638
+            return true;
639
+        }
640
+    }
641
+
642
+    /**
643
+     * Returns a private setting value
644
+     *
645
+     * @param string $name Name of the private setting
646
+     *
647
+     * @return mixed Null if the setting does not exist
648
+     */
649
+    public function getPrivateSetting($name) {
650
+        if ((int) ($this->guid) > 0) {
651
+            return get_private_setting($this->getGUID(), $name);
652
+        } else {
653
+            if (isset($this->temp_private_settings[$name])) {
654
+                return $this->temp_private_settings[$name];
655
+            }
656
+        }
657
+        return null;
658
+    }
659
+
660
+    /**
661
+     * Removes private setting
662
+     *
663
+     * @param string $name Name of the private setting
664
+     *
665
+     * @return bool
666
+     */
667
+    public function removePrivateSetting($name) {
668
+        return remove_private_setting($this->getGUID(), $name);
669
+    }
670
+
671
+    /**
672
+     * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
673
+     * If you pass a name, only annotations matching that name will be deleted.
674
+     *
675
+     * @warning Calling this with no or empty arguments will clear all annotations on the entity.
676
+     *
677
+     * @param null|string $name The annotations name to remove.
678
+     * @return bool
679
+     * @since 1.8
680
+     */
681
+    public function deleteAnnotations($name = null) {
682
+        $options = [
683
+            'guid' => $this->guid,
684
+            'limit' => 0
685
+        ];
686
+        if ($name) {
687
+            $options['annotation_name'] = $name;
688
+        }
689
+
690
+        return elgg_delete_annotations($options);
691
+    }
692
+
693
+    /**
694
+     * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
695
+     * If you pass a name, only annotations matching that name will be deleted.
696
+     *
697
+     * @param null|string $name The name of annotations to delete.
698
+     * @return bool
699
+     * @since 1.8
700
+     */
701
+    public function deleteOwnedAnnotations($name = null) {
702
+        // access is turned off for this because they might
703
+        // no longer have access to an entity they created annotations on.
704
+        $ia = elgg_set_ignore_access(true);
705
+        $options = [
706
+            'annotation_owner_guid' => $this->guid,
707
+            'limit' => 0
708
+        ];
709
+        if ($name) {
710
+            $options['annotation_name'] = $name;
711
+        }
712
+
713
+        $r = elgg_delete_annotations($options);
714
+        elgg_set_ignore_access($ia);
715
+        return $r;
716
+    }
717
+
718
+    /**
719
+     * Disables annotations for this entity, optionally based on name.
720
+     *
721
+     * @param string $name An options name of annotations to disable.
722
+     * @return bool
723
+     * @since 1.8
724
+     */
725
+    public function disableAnnotations($name = '') {
726
+        $options = [
727
+            'guid' => $this->guid,
728
+            'limit' => 0
729
+        ];
730
+        if ($name) {
731
+            $options['annotation_name'] = $name;
732
+        }
733
+
734
+        return elgg_disable_annotations($options);
735
+    }
736
+
737
+    /**
738
+     * Enables annotations for this entity, optionally based on name.
739
+     *
740
+     * @warning Before calling this, you must use {@link access_show_hidden_entities()}
741
+     *
742
+     * @param string $name An options name of annotations to enable.
743
+     * @return bool
744
+     * @since 1.8
745
+     */
746
+    public function enableAnnotations($name = '') {
747
+        $options = [
748
+            'guid' => $this->guid,
749
+            'limit' => 0
750
+        ];
751
+        if ($name) {
752
+            $options['annotation_name'] = $name;
753
+        }
754
+
755
+        return elgg_enable_annotations($options);
756
+    }
757
+
758
+    /**
759
+     * Helper function to return annotation calculation results
760
+     *
761
+     * @param string $name        The annotation name.
762
+     * @param string $calculation A valid MySQL function to run its values through
763
+     * @return mixed
764
+     */
765
+    private function getAnnotationCalculation($name, $calculation) {
766
+        $options = [
767
+            'guid' => $this->getGUID(),
768
+            'distinct' => false,
769
+            'annotation_name' => $name,
770
+            'annotation_calculation' => $calculation
771
+        ];
772
+
773
+        return elgg_get_annotations($options);
774
+    }
775
+
776
+    /**
777
+     * Adds an annotation to an entity.
778
+     *
779
+     * @warning By default, annotations are private.
780
+     *
781
+     * @warning Annotating an unsaved entity more than once with the same name
782
+     *          will only save the last annotation.
783
+     *
784
+     * @param string $name       Annotation name
785
+     * @param mixed  $value      Annotation value
786
+     * @param int    $access_id  Access ID
787
+     * @param int    $owner_guid GUID of the annotation owner
788
+     * @param string $vartype    The type of annotation value
789
+     *
790
+     * @return bool|int Returns int if an annotation is saved
791
+     */
792
+    public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
793
+        if ((int) $this->guid > 0) {
794
+            return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
795
+        } else {
796
+            $this->temp_annotations[$name] = $value;
797
+        }
798
+        return true;
799
+    }
800
+
801
+    /**
802
+     * Gets an array of annotations.
803
+     *
804
+     * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
805
+     * as the options array.
806
+     *
807
+     * @param array $options Array of options for elgg_get_annotations() except guid.
808
+     *
809
+     * @return array
810
+     * @see elgg_get_annotations()
811
+     */
812
+    public function getAnnotations(array $options = []) {
813
+        if ($this->guid) {
814
+            $options['guid'] = $this->guid;
815
+
816
+            return elgg_get_annotations($options);
817
+        } else {
818
+            $name = elgg_extract('annotation_name', $options, '');
819
+
820
+            if (isset($this->temp_annotations[$name])) {
821
+                return [$this->temp_annotations[$name]];
822
+            }
823
+        }
824
+
825
+        return [];
826
+    }
827
+
828
+    /**
829
+     * Count annotations.
830
+     *
831
+     * @param string $name The type of annotation.
832
+     *
833
+     * @return int
834
+     */
835
+    public function countAnnotations($name = "") {
836
+        return $this->getAnnotationCalculation($name, 'count');
837
+    }
838
+
839
+    /**
840
+     * Get the average of an integer type annotation.
841
+     *
842
+     * @param string $name Annotation name
843
+     *
844
+     * @return int
845
+     */
846
+    public function getAnnotationsAvg($name) {
847
+        return $this->getAnnotationCalculation($name, 'avg');
848
+    }
849
+
850
+    /**
851
+     * Get the sum of integer type annotations of a given name.
852
+     *
853
+     * @param string $name Annotation name
854
+     *
855
+     * @return int
856
+     */
857
+    public function getAnnotationsSum($name) {
858
+        return $this->getAnnotationCalculation($name, 'sum');
859
+    }
860
+
861
+    /**
862
+     * Get the minimum of integer type annotations of given name.
863
+     *
864
+     * @param string $name Annotation name
865
+     *
866
+     * @return int
867
+     */
868
+    public function getAnnotationsMin($name) {
869
+        return $this->getAnnotationCalculation($name, 'min');
870
+    }
871
+
872
+    /**
873
+     * Get the maximum of integer type annotations of a given name.
874
+     *
875
+     * @param string $name Annotation name
876
+     *
877
+     * @return int
878
+     */
879
+    public function getAnnotationsMax($name) {
880
+        return $this->getAnnotationCalculation($name, 'max');
881
+    }
882
+
883
+    /**
884
+     * Count the number of comments attached to this entity.
885
+     *
886
+     * @return int Number of comments
887
+     * @since 1.8.0
888
+     */
889
+    public function countComments() {
890
+        $params = ['entity' => $this];
891
+        $num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
892
+
893
+        if (is_int($num)) {
894
+            return $num;
895
+        } else {
896
+            return elgg_get_entities([
897
+                'type' => 'object',
898
+                'subtype' => 'comment',
899
+                'container_guid' => $this->getGUID(),
900
+                'count' => true,
901
+                'distinct' => false,
902
+            ]);
903
+        }
904
+    }
905
+
906
+    /**
907
+     * Gets an array of entities with a relationship to this entity.
908
+     *
909
+     * @param array $options Options array. See elgg_get_entities_from_relationship()
910
+     *                       for a list of options. 'relationship_guid' is set to
911
+     *                       this entity.
912
+     *
913
+     * @return array|false An array of entities or false on failure
914
+     * @see elgg_get_entities_from_relationship()
915
+     */
916
+    public function getEntitiesFromRelationship(array $options = []) {
917
+        $options['relationship_guid'] = $this->guid;
918
+        return elgg_get_entities_from_relationship($options);
919
+    }
920
+
921
+    /**
922
+     * Gets the number of entities from a specific relationship type
923
+     *
924
+     * @param string $relationship         Relationship type (eg "friends")
925
+     * @param bool   $inverse_relationship Invert relationship
926
+     *
927
+     * @return int|false The number of entities or false on failure
928
+     */
929
+    public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
930
+        return elgg_get_entities_from_relationship([
931
+            'relationship' => $relationship,
932
+            'relationship_guid' => $this->getGUID(),
933
+            'inverse_relationship' => $inverse_relationship,
934
+            'count' => true
935
+        ]);
936
+    }
937
+
938
+    /**
939
+     * Can a user edit this entity?
940
+     *
941
+     * @tip Can be overridden by registering for the permissions_check plugin hook.
942
+     *
943
+     * @param int $user_guid The user GUID, optionally (default: logged in user)
944
+     *
945
+     * @return bool Whether this entity is editable by the given user.
946
+     * @see elgg_set_ignore_access()
947
+     */
948
+    public function canEdit($user_guid = 0) {
949
+        return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
950
+    }
951
+
952
+    /**
953
+     * Can a user delete this entity?
954
+     *
955
+     * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
956
+     *
957
+     * @param int $user_guid The user GUID, optionally (default: logged in user)
958
+     *
959
+     * @return bool Whether this entity is deletable by the given user.
960
+     * @since 1.11
961
+     * @see elgg_set_ignore_access()
962
+     */
963
+    public function canDelete($user_guid = 0) {
964
+        return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
965
+    }
966
+
967
+    /**
968
+     * Can a user edit metadata on this entity?
969
+     *
970
+     * If no specific metadata is passed, it returns whether the user can
971
+     * edit any metadata on the entity.
972
+     *
973
+     * @tip Can be overridden by by registering for the permissions_check:metadata
974
+     * plugin hook.
975
+     *
976
+     * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
977
+     * @param int           $user_guid The user GUID, optionally (default: logged in user)
978
+     *
979
+     * @return bool
980
+     * @see elgg_set_ignore_access()
981
+     */
982
+    public function canEditMetadata($metadata = null, $user_guid = 0) {
983
+        return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
984
+    }
985
+
986
+    /**
987
+     * Can a user add an entity to this container
988
+     *
989
+     * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
990
+     * @param string $type      The type of entity we're looking to write
991
+     * @param string $subtype   The subtype of the entity we're looking to write
992
+     *
993
+     * @return bool
994
+     * @see elgg_set_ignore_access()
995
+     */
996
+    public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
997
+        return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
998
+    }
999
+
1000
+    /**
1001
+     * Can a user comment on an entity?
1002
+     *
1003
+     * @tip Can be overridden by registering for the permissions_check:comment,
1004
+     * <entity type> plugin hook.
1005
+     *
1006
+     * @param int  $user_guid User guid (default is logged in user)
1007
+     * @param bool $default   Default permission
1008
+     * @return bool
1009
+     */
1010
+    public function canComment($user_guid = 0, $default = null) {
1011
+        return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1012
+    }
1013
+
1014
+    /**
1015
+     * Can a user annotate an entity?
1016
+     *
1017
+     * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1018
+     * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1019
+     *
1020
+     * @tip If you want logged out users to annotate an object, do not call
1021
+     * canAnnotate(). It's easier than using the plugin hook.
1022
+     *
1023
+     * @param int    $user_guid       User guid (default is logged in user)
1024
+     * @param string $annotation_name The name of the annotation (default is unspecified)
1025
+     *
1026
+     * @return bool
1027
+     */
1028
+    public function canAnnotate($user_guid = 0, $annotation_name = '') {
1029
+        return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1030
+    }
1031
+
1032
+    /**
1033
+     * Returns the access_id.
1034
+     *
1035
+     * @return int The access ID
1036
+     */
1037
+    public function getAccessID() {
1038
+        return $this->access_id;
1039
+    }
1040
+
1041
+    /**
1042
+     * Returns the guid.
1043
+     *
1044
+     * @return int|null GUID
1045
+     */
1046
+    public function getGUID() {
1047
+        return $this->guid;
1048
+    }
1049
+
1050
+    /**
1051
+     * Returns the entity type
1052
+     *
1053
+     * @return string The entity type
1054
+     */
1055
+    public function getType() {
1056
+        return $this->type;
1057
+    }
1058
+
1059
+    /**
1060
+     * Get the entity subtype
1061
+     *
1062
+     * @return string The entity subtype
1063
+     */
1064
+    public function getSubtype() {
1065
+        // If this object hasn't been saved, then return the subtype string.
1066
+        if ($this->attributes['guid']) {
1067
+            return get_subtype_from_id($this->attributes['subtype']);
1068
+        }
1069
+        return $this->attributes['subtype'];
1070
+    }
1071
+
1072
+    /**
1073
+     * Get the guid of the entity's owner.
1074
+     *
1075
+     * @return int The owner GUID
1076
+     */
1077
+    public function getOwnerGUID() {
1078
+        return (int) $this->owner_guid;
1079
+    }
1080
+
1081
+    /**
1082
+     * Gets the \ElggEntity that owns this entity.
1083
+     *
1084
+     * @return \ElggEntity The owning entity
1085
+     */
1086
+    public function getOwnerEntity() {
1087
+        return get_entity($this->owner_guid);
1088
+    }
1089
+
1090
+    /**
1091
+     * Set the container for this object.
1092
+     *
1093
+     * @param int $container_guid The ID of the container.
1094
+     *
1095
+     * @return bool
1096
+     */
1097
+    public function setContainerGUID($container_guid) {
1098
+        return $this->container_guid = (int) $container_guid;
1099
+    }
1100
+
1101
+    /**
1102
+     * Gets the container GUID for this entity.
1103
+     *
1104
+     * @return int
1105
+     */
1106
+    public function getContainerGUID() {
1107
+        return (int) $this->container_guid;
1108
+    }
1109
+
1110
+    /**
1111
+     * Get the container entity for this object.
1112
+     *
1113
+     * @return \ElggEntity
1114
+     * @since 1.8.0
1115
+     */
1116
+    public function getContainerEntity() {
1117
+        return get_entity($this->getContainerGUID());
1118
+    }
1119
+
1120
+    /**
1121
+     * Returns the UNIX epoch time that this entity was last updated
1122
+     *
1123
+     * @return int UNIX epoch time
1124
+     */
1125
+    public function getTimeUpdated() {
1126
+        return $this->time_updated;
1127
+    }
1128
+
1129
+    /**
1130
+     * Gets the URL for this entity.
1131
+     *
1132
+     * Plugins can register for the 'entity:url', <type> plugin hook to
1133
+     * customize the url for an entity.
1134
+     *
1135
+     * @return string The URL of the entity
1136
+     */
1137
+    public function getURL() {
1138
+        $url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
1139 1139
 		
1140
-		if ($url === null || $url === '' || $url === false) {
1141
-			return '';
1142
-		}
1143
-
1144
-		return elgg_normalize_url($url);
1145
-	}
1146
-
1147
-	/**
1148
-	 * Saves icons using an uploaded file as the source.
1149
-	 *
1150
-	 * @param string $input_name Form input name
1151
-	 * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1152
-	 * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1153
-	 * @return bool
1154
-	 */
1155
-	public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1156
-		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1157
-	}
1158
-
1159
-	/**
1160
-	 * Saves icons using a local file as the source.
1161
-	 *
1162
-	 * @param string $filename The full path to the local file
1163
-	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1164
-	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1165
-	 * @return bool
1166
-	 */
1167
-	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1168
-		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1169
-	}
1170
-
1171
-	/**
1172
-	 * Saves icons using a file located in the data store as the source.
1173
-	 *
1174
-	 * @param string $file   An ElggFile instance
1175
-	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1176
-	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1177
-	 * @return bool
1178
-	 */
1179
-	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1180
-		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1181
-	}
1140
+        if ($url === null || $url === '' || $url === false) {
1141
+            return '';
1142
+        }
1143
+
1144
+        return elgg_normalize_url($url);
1145
+    }
1146
+
1147
+    /**
1148
+     * Saves icons using an uploaded file as the source.
1149
+     *
1150
+     * @param string $input_name Form input name
1151
+     * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1152
+     * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1153
+     * @return bool
1154
+     */
1155
+    public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1156
+        return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1157
+    }
1158
+
1159
+    /**
1160
+     * Saves icons using a local file as the source.
1161
+     *
1162
+     * @param string $filename The full path to the local file
1163
+     * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1164
+     * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1165
+     * @return bool
1166
+     */
1167
+    public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1168
+        return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1169
+    }
1170
+
1171
+    /**
1172
+     * Saves icons using a file located in the data store as the source.
1173
+     *
1174
+     * @param string $file   An ElggFile instance
1175
+     * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1176
+     * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1177
+     * @return bool
1178
+     */
1179
+    public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1180
+        return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1181
+    }
1182 1182
 	
1183
-	/**
1184
-	 * Returns entity icon as an ElggIcon object
1185
-	 * The icon file may or may not exist on filestore
1186
-	 *
1187
-	 * @param string $size Size of the icon
1188
-	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1189
-	 * @return \ElggIcon
1190
-	 */
1191
-	public function getIcon($size, $type = 'icon') {
1192
-		return _elgg_services()->iconService->getIcon($this, $size, $type);
1193
-	}
1194
-
1195
-	/**
1196
-	 * Removes all icon files and metadata for the passed type of icon.
1197
-	 *
1198
-	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1199
-	 * @return bool
1200
-	 */
1201
-	public function deleteIcon($type = 'icon') {
1202
-		return _elgg_services()->iconService->deleteIcon($this, $type);
1203
-	}
1183
+    /**
1184
+     * Returns entity icon as an ElggIcon object
1185
+     * The icon file may or may not exist on filestore
1186
+     *
1187
+     * @param string $size Size of the icon
1188
+     * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1189
+     * @return \ElggIcon
1190
+     */
1191
+    public function getIcon($size, $type = 'icon') {
1192
+        return _elgg_services()->iconService->getIcon($this, $size, $type);
1193
+    }
1194
+
1195
+    /**
1196
+     * Removes all icon files and metadata for the passed type of icon.
1197
+     *
1198
+     * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1199
+     * @return bool
1200
+     */
1201
+    public function deleteIcon($type = 'icon') {
1202
+        return _elgg_services()->iconService->deleteIcon($this, $type);
1203
+    }
1204 1204
 	
1205
-	/**
1206
-	 * Returns the timestamp of when the icon was changed.
1207
-	 *
1208
-	 * @param string $size The size of the icon
1209
-	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1210
-	 *
1211
-	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1212
-	 */
1213
-	public function getIconLastChange($size, $type = 'icon') {
1214
-		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1215
-	}
1205
+    /**
1206
+     * Returns the timestamp of when the icon was changed.
1207
+     *
1208
+     * @param string $size The size of the icon
1209
+     * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1210
+     *
1211
+     * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1212
+     */
1213
+    public function getIconLastChange($size, $type = 'icon') {
1214
+        return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1215
+    }
1216 1216
 	
1217
-	/**
1218
-	 * Returns if the entity has an icon of the passed type.
1219
-	 *
1220
-	 * @param string $size The size of the icon
1221
-	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1222
-	 * @return bool
1223
-	 */
1224
-	public function hasIcon($size, $type = 'icon') {
1225
-		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1226
-	}
1227
-
1228
-	/**
1229
-	 * Get the URL for this entity's icon
1230
-	 *
1231
-	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1232
-	 * to customize the icon for an entity.
1233
-	 *
1234
-	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1235
-	 *                      or an array of parameters including 'size'
1236
-	 * @return string The URL
1237
-	 * @since 1.8.0
1238
-	 */
1239
-	public function getIconURL($params = []) {
1240
-		return _elgg_services()->iconService->getIconURL($this, $params);
1241
-	}
1242
-
1243
-	/**
1244
-	 * Save an entity.
1245
-	 *
1246
-	 * @return bool|int
1247
-	 * @throws InvalidParameterException
1248
-	 * @throws IOException
1249
-	 */
1250
-	public function save() {
1251
-		$guid = $this->guid;
1252
-		if ($guid > 0) {
1253
-			$guid = $this->update();
1254
-		} else {
1255
-			$guid = $this->create();
1256
-			if ($guid && !_elgg_services()->events->trigger('create', $this->type, $this)) {
1257
-				// plugins that return false to event don't need to override the access system
1258
-				$ia = elgg_set_ignore_access(true);
1259
-				$this->delete();
1260
-				elgg_set_ignore_access($ia);
1261
-				return false;
1262
-			}
1263
-		}
1264
-
1265
-		if ($guid) {
1266
-			_elgg_services()->entityCache->set($this);
1267
-			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1268
-		}
1269
-
1270
-		return $guid;
1271
-	}
1217
+    /**
1218
+     * Returns if the entity has an icon of the passed type.
1219
+     *
1220
+     * @param string $size The size of the icon
1221
+     * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1222
+     * @return bool
1223
+     */
1224
+    public function hasIcon($size, $type = 'icon') {
1225
+        return _elgg_services()->iconService->hasIcon($this, $size, $type);
1226
+    }
1227
+
1228
+    /**
1229
+     * Get the URL for this entity's icon
1230
+     *
1231
+     * Plugins can register for the 'entity:icon:url', <type> plugin hook
1232
+     * to customize the icon for an entity.
1233
+     *
1234
+     * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1235
+     *                      or an array of parameters including 'size'
1236
+     * @return string The URL
1237
+     * @since 1.8.0
1238
+     */
1239
+    public function getIconURL($params = []) {
1240
+        return _elgg_services()->iconService->getIconURL($this, $params);
1241
+    }
1242
+
1243
+    /**
1244
+     * Save an entity.
1245
+     *
1246
+     * @return bool|int
1247
+     * @throws InvalidParameterException
1248
+     * @throws IOException
1249
+     */
1250
+    public function save() {
1251
+        $guid = $this->guid;
1252
+        if ($guid > 0) {
1253
+            $guid = $this->update();
1254
+        } else {
1255
+            $guid = $this->create();
1256
+            if ($guid && !_elgg_services()->events->trigger('create', $this->type, $this)) {
1257
+                // plugins that return false to event don't need to override the access system
1258
+                $ia = elgg_set_ignore_access(true);
1259
+                $this->delete();
1260
+                elgg_set_ignore_access($ia);
1261
+                return false;
1262
+            }
1263
+        }
1264
+
1265
+        if ($guid) {
1266
+            _elgg_services()->entityCache->set($this);
1267
+            $this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1268
+        }
1269
+
1270
+        return $guid;
1271
+    }
1272 1272
 	
1273
-	/**
1274
-	 * Create a new entry in the entities table.
1275
-	 *
1276
-	 * Saves the base information in the entities table for the entity.  Saving
1277
-	 * the type-specific information is handled in the calling class method.
1278
-	 *
1279
-	 * @warning Entities must have an entry in both the entities table and their type table
1280
-	 * or they will throw an exception when loaded.
1281
-	 *
1282
-	 * @return int The new entity's GUID
1283
-	 * @throws InvalidParameterException If the entity's type has not been set.
1284
-	 * @throws IOException If the new row fails to write to the DB.
1285
-	 */
1286
-	protected function create() {
1287
-
1288
-		$allowed_types = elgg_get_config('entity_types');
1289
-		$type = $this->getDatabase()->sanitizeString($this->attributes['type']);
1290
-		if (!in_array($type, $allowed_types)) {
1291
-			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1292
-					. implode(', ', $allowed_types));
1293
-		}
1273
+    /**
1274
+     * Create a new entry in the entities table.
1275
+     *
1276
+     * Saves the base information in the entities table for the entity.  Saving
1277
+     * the type-specific information is handled in the calling class method.
1278
+     *
1279
+     * @warning Entities must have an entry in both the entities table and their type table
1280
+     * or they will throw an exception when loaded.
1281
+     *
1282
+     * @return int The new entity's GUID
1283
+     * @throws InvalidParameterException If the entity's type has not been set.
1284
+     * @throws IOException If the new row fails to write to the DB.
1285
+     */
1286
+    protected function create() {
1287
+
1288
+        $allowed_types = elgg_get_config('entity_types');
1289
+        $type = $this->getDatabase()->sanitizeString($this->attributes['type']);
1290
+        if (!in_array($type, $allowed_types)) {
1291
+            throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1292
+                    . implode(', ', $allowed_types));
1293
+        }
1294 1294
 		
1295
-		$subtype = $this->attributes['subtype'];
1296
-		$subtype_id = add_subtype($type, $subtype);
1297
-		$owner_guid = (int) $this->attributes['owner_guid'];
1298
-		$access_id = (int) $this->attributes['access_id'];
1299
-		$now = $this->getCurrentTime()->getTimestamp();
1300
-		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1295
+        $subtype = $this->attributes['subtype'];
1296
+        $subtype_id = add_subtype($type, $subtype);
1297
+        $owner_guid = (int) $this->attributes['owner_guid'];
1298
+        $access_id = (int) $this->attributes['access_id'];
1299
+        $now = $this->getCurrentTime()->getTimestamp();
1300
+        $time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1301 1301
 		
1302
-		$container_guid = $this->attributes['container_guid'];
1303
-		if ($container_guid == 0) {
1304
-			$container_guid = $owner_guid;
1305
-			$this->attributes['container_guid'] = $container_guid;
1306
-		}
1307
-		$container_guid = (int) $container_guid;
1308
-
1309
-		if ($access_id == ACCESS_DEFAULT) {
1310
-			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1311
-		}
1312
-
1313
-		$user_guid = elgg_get_logged_in_user_guid();
1314
-
1315
-		// If given an owner, verify it can be loaded
1316
-		if ($owner_guid) {
1317
-			$owner = $this->getOwnerEntity();
1318
-			if (!$owner) {
1319
-				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1320
-					. " owner $owner_guid could not be loaded.");
1321
-				return false;
1322
-			}
1323
-
1324
-			// If different owner than logged in, verify can write to container.
1325
-
1326
-			if ($user_guid != $owner_guid && !$owner->canWriteToContainer(0, $type, $subtype)) {
1327
-				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1328
-					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1329
-				return false;
1330
-			}
1331
-		}
1332
-
1333
-		// If given a container, verify it can be loaded and that the current user can write to it
1334
-		if ($container_guid) {
1335
-			$container = $this->getContainerEntity();
1336
-			if (!$container) {
1337
-				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1338
-					. " container $container_guid could not be loaded.");
1339
-				return false;
1340
-			}
1341
-
1342
-			if (!$container->canWriteToContainer(0, $type, $subtype)) {
1343
-				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1344
-					. " permitted to write to container $container_guid.");
1345
-				return false;
1346
-			}
1347
-		}
1348
-
1349
-		$result = _elgg_services()->entityTable->insertRow((object) [
1350
-			'type' => $type,
1351
-			'subtype_id' => $subtype_id,
1352
-			'owner_guid' => $owner_guid,
1353
-			'container_guid' => $container_guid,
1354
-			'access_id' => $access_id,
1355
-			'time_created' => $time_created,
1356
-			'time_updated' => $now,
1357
-			'last_action' => $now,
1358
-		], $this->attributes);
1359
-
1360
-		if (!$result) {
1361
-			throw new \IOException("Unable to save new object's base entity information!");
1362
-		}
1302
+        $container_guid = $this->attributes['container_guid'];
1303
+        if ($container_guid == 0) {
1304
+            $container_guid = $owner_guid;
1305
+            $this->attributes['container_guid'] = $container_guid;
1306
+        }
1307
+        $container_guid = (int) $container_guid;
1308
+
1309
+        if ($access_id == ACCESS_DEFAULT) {
1310
+            throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1311
+        }
1312
+
1313
+        $user_guid = elgg_get_logged_in_user_guid();
1314
+
1315
+        // If given an owner, verify it can be loaded
1316
+        if ($owner_guid) {
1317
+            $owner = $this->getOwnerEntity();
1318
+            if (!$owner) {
1319
+                _elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1320
+                    . " owner $owner_guid could not be loaded.");
1321
+                return false;
1322
+            }
1323
+
1324
+            // If different owner than logged in, verify can write to container.
1325
+
1326
+            if ($user_guid != $owner_guid && !$owner->canWriteToContainer(0, $type, $subtype)) {
1327
+                _elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1328
+                    . " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1329
+                return false;
1330
+            }
1331
+        }
1332
+
1333
+        // If given a container, verify it can be loaded and that the current user can write to it
1334
+        if ($container_guid) {
1335
+            $container = $this->getContainerEntity();
1336
+            if (!$container) {
1337
+                _elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1338
+                    . " container $container_guid could not be loaded.");
1339
+                return false;
1340
+            }
1341
+
1342
+            if (!$container->canWriteToContainer(0, $type, $subtype)) {
1343
+                _elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1344
+                    . " permitted to write to container $container_guid.");
1345
+                return false;
1346
+            }
1347
+        }
1348
+
1349
+        $result = _elgg_services()->entityTable->insertRow((object) [
1350
+            'type' => $type,
1351
+            'subtype_id' => $subtype_id,
1352
+            'owner_guid' => $owner_guid,
1353
+            'container_guid' => $container_guid,
1354
+            'access_id' => $access_id,
1355
+            'time_created' => $time_created,
1356
+            'time_updated' => $now,
1357
+            'last_action' => $now,
1358
+        ], $this->attributes);
1359
+
1360
+        if (!$result) {
1361
+            throw new \IOException("Unable to save new object's base entity information!");
1362
+        }
1363 1363
 	
1364
-		// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1365
-		$this->attributes['subtype'] = (int) $subtype_id;
1366
-		$this->attributes['guid'] = (int) $result;
1367
-		$this->attributes['time_created'] = (int) $time_created;
1368
-		$this->attributes['time_updated'] = (int) $now;
1369
-		$this->attributes['last_action'] = (int) $now;
1370
-		$this->attributes['container_guid'] = (int) $container_guid;
1371
-
1372
-		// We are writing this new entity to cache to make sure subsequent calls
1373
-		// to get_entity() load entity from cache and not from the DB
1374
-		// At this point, secondary attributes have not yet been written to the DB,
1375
-		// but metadata and annotation event handlers may be calling get_entity()
1376
-		_elgg_services()->entityCache->set($this);
1377
-
1378
-		// Save any unsaved metadata
1379
-		if (sizeof($this->temp_metadata) > 0) {
1380
-			foreach ($this->temp_metadata as $name => $value) {
1381
-				$this->$name = $value;
1382
-			}
1364
+        // for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1365
+        $this->attributes['subtype'] = (int) $subtype_id;
1366
+        $this->attributes['guid'] = (int) $result;
1367
+        $this->attributes['time_created'] = (int) $time_created;
1368
+        $this->attributes['time_updated'] = (int) $now;
1369
+        $this->attributes['last_action'] = (int) $now;
1370
+        $this->attributes['container_guid'] = (int) $container_guid;
1371
+
1372
+        // We are writing this new entity to cache to make sure subsequent calls
1373
+        // to get_entity() load entity from cache and not from the DB
1374
+        // At this point, secondary attributes have not yet been written to the DB,
1375
+        // but metadata and annotation event handlers may be calling get_entity()
1376
+        _elgg_services()->entityCache->set($this);
1377
+
1378
+        // Save any unsaved metadata
1379
+        if (sizeof($this->temp_metadata) > 0) {
1380
+            foreach ($this->temp_metadata as $name => $value) {
1381
+                $this->$name = $value;
1382
+            }
1383 1383
 			
1384
-			$this->temp_metadata = [];
1385
-		}
1386
-
1387
-		// Save any unsaved annotations.
1388
-		if (sizeof($this->temp_annotations) > 0) {
1389
-			foreach ($this->temp_annotations as $name => $value) {
1390
-				$this->annotate($name, $value);
1391
-			}
1384
+            $this->temp_metadata = [];
1385
+        }
1386
+
1387
+        // Save any unsaved annotations.
1388
+        if (sizeof($this->temp_annotations) > 0) {
1389
+            foreach ($this->temp_annotations as $name => $value) {
1390
+                $this->annotate($name, $value);
1391
+            }
1392 1392
 			
1393
-			$this->temp_annotations = [];
1394
-		}
1395
-
1396
-		// Save any unsaved private settings.
1397
-		if (sizeof($this->temp_private_settings) > 0) {
1398
-			foreach ($this->temp_private_settings as $name => $value) {
1399
-				$this->setPrivateSetting($name, $value);
1400
-			}
1393
+            $this->temp_annotations = [];
1394
+        }
1395
+
1396
+        // Save any unsaved private settings.
1397
+        if (sizeof($this->temp_private_settings) > 0) {
1398
+            foreach ($this->temp_private_settings as $name => $value) {
1399
+                $this->setPrivateSetting($name, $value);
1400
+            }
1401 1401
 			
1402
-			$this->temp_private_settings = [];
1403
-		}
1402
+            $this->temp_private_settings = [];
1403
+        }
1404 1404
 		
1405
-		return $result;
1406
-	}
1407
-
1408
-	/**
1409
-	 * Update the entity in the database.
1410
-	 *
1411
-	 * @return bool Whether the update was successful.
1412
-	 *
1413
-	 * @throws InvalidParameterException
1414
-	 */
1415
-	protected function update() {
1405
+        return $result;
1406
+    }
1407
+
1408
+    /**
1409
+     * Update the entity in the database.
1410
+     *
1411
+     * @return bool Whether the update was successful.
1412
+     *
1413
+     * @throws InvalidParameterException
1414
+     */
1415
+    protected function update() {
1416 1416
 		
1417
-		_elgg_services()->boot->invalidateCache($this->guid);
1418
-
1419
-		if (!$this->canEdit()) {
1420
-			return false;
1421
-		}
1422
-
1423
-		// give old update event a chance to stop the update
1424
-		if (!_elgg_services()->events->trigger('update', $this->type, $this)) {
1425
-			return false;
1426
-		}
1427
-
1428
-		// See #6225. We copy these after the update event in case a handler changed one of them.
1429
-		$guid = (int) $this->guid;
1430
-		$owner_guid = (int) $this->owner_guid;
1431
-		$access_id = (int) $this->access_id;
1432
-		$container_guid = (int) $this->container_guid;
1433
-		$time_created = (int) $this->time_created;
1434
-		$time = $this->getCurrentTime()->getTimestamp();
1435
-
1436
-		if ($access_id == ACCESS_DEFAULT) {
1437
-			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1438
-		}
1439
-
1440
-		$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1441
-			'owner_guid' => $owner_guid,
1442
-			'container_guid' => $container_guid,
1443
-			'access_id' => $access_id,
1444
-			'time_created' => $time_created,
1445
-			'time_updated' => $time,
1446
-			'guid' => $guid,
1447
-		]);
1448
-
1449
-		elgg_trigger_after_event('update', $this->type, $this);
1450
-
1451
-		// TODO(evan): Move this to \ElggObject?
1452
-		if ($this instanceof \ElggObject) {
1453
-			update_river_access_by_object($guid, $access_id);
1454
-		}
1455
-
1456
-		if ($ret !== false) {
1457
-			$this->attributes['time_updated'] = $time;
1458
-		}
1459
-
1460
-		$this->orig_attributes = [];
1461
-
1462
-		// Handle cases where there was no error BUT no rows were updated!
1463
-		return $ret !== false;
1464
-	}
1465
-
1466
-	/**
1467
-	 * Loads attributes from the entities table into the object.
1468
-	 *
1469
-	 * @param mixed $guid GUID of entity or \stdClass object from entities table
1470
-	 *
1471
-	 * @return bool
1472
-	 */
1473
-	protected function load($guid) {
1474
-		if ($guid instanceof \stdClass) {
1475
-			$row = $guid;
1476
-		} else {
1477
-			$row = get_entity_as_row($guid);
1478
-		}
1479
-
1480
-		if ($row) {
1481
-			// Create the array if necessary - all subclasses should test before creating
1482
-			if (!is_array($this->attributes)) {
1483
-				$this->attributes = [];
1484
-			}
1485
-
1486
-			// Now put these into the attributes array as core values
1487
-			$objarray = (array) $row;
1488
-			foreach ($objarray as $key => $value) {
1489
-				$this->attributes[$key] = $value;
1490
-			}
1491
-
1492
-			// guid needs to be an int  https://github.com/elgg/elgg/issues/4111
1493
-			$this->attributes['guid'] = (int) $this->attributes['guid'];
1494
-
1495
-			// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1496
-			$this->attributes['subtype'] = (int) $this->attributes['subtype'];
1497
-
1498
-			// Cache object handle
1499
-			if ($this->attributes['guid']) {
1500
-				_elgg_services()->entityCache->set($this);
1501
-			}
1502
-
1503
-			return true;
1504
-		}
1505
-
1506
-		return false;
1507
-	}
1508
-
1509
-	/**
1510
-	 * Stores non-attributes from the loading of the entity as volatile data
1511
-	 *
1512
-	 * @param array $data Key value array
1513
-	 * @return void
1514
-	 */
1515
-	protected function loadAdditionalSelectValues(array $data) {
1516
-		foreach ($data as $name => $value) {
1517
-			$this->setVolatileData("select:$name", $value);
1518
-		}
1519
-	}
1417
+        _elgg_services()->boot->invalidateCache($this->guid);
1418
+
1419
+        if (!$this->canEdit()) {
1420
+            return false;
1421
+        }
1422
+
1423
+        // give old update event a chance to stop the update
1424
+        if (!_elgg_services()->events->trigger('update', $this->type, $this)) {
1425
+            return false;
1426
+        }
1427
+
1428
+        // See #6225. We copy these after the update event in case a handler changed one of them.
1429
+        $guid = (int) $this->guid;
1430
+        $owner_guid = (int) $this->owner_guid;
1431
+        $access_id = (int) $this->access_id;
1432
+        $container_guid = (int) $this->container_guid;
1433
+        $time_created = (int) $this->time_created;
1434
+        $time = $this->getCurrentTime()->getTimestamp();
1435
+
1436
+        if ($access_id == ACCESS_DEFAULT) {
1437
+            throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1438
+        }
1439
+
1440
+        $ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1441
+            'owner_guid' => $owner_guid,
1442
+            'container_guid' => $container_guid,
1443
+            'access_id' => $access_id,
1444
+            'time_created' => $time_created,
1445
+            'time_updated' => $time,
1446
+            'guid' => $guid,
1447
+        ]);
1448
+
1449
+        elgg_trigger_after_event('update', $this->type, $this);
1450
+
1451
+        // TODO(evan): Move this to \ElggObject?
1452
+        if ($this instanceof \ElggObject) {
1453
+            update_river_access_by_object($guid, $access_id);
1454
+        }
1455
+
1456
+        if ($ret !== false) {
1457
+            $this->attributes['time_updated'] = $time;
1458
+        }
1459
+
1460
+        $this->orig_attributes = [];
1461
+
1462
+        // Handle cases where there was no error BUT no rows were updated!
1463
+        return $ret !== false;
1464
+    }
1465
+
1466
+    /**
1467
+     * Loads attributes from the entities table into the object.
1468
+     *
1469
+     * @param mixed $guid GUID of entity or \stdClass object from entities table
1470
+     *
1471
+     * @return bool
1472
+     */
1473
+    protected function load($guid) {
1474
+        if ($guid instanceof \stdClass) {
1475
+            $row = $guid;
1476
+        } else {
1477
+            $row = get_entity_as_row($guid);
1478
+        }
1479
+
1480
+        if ($row) {
1481
+            // Create the array if necessary - all subclasses should test before creating
1482
+            if (!is_array($this->attributes)) {
1483
+                $this->attributes = [];
1484
+            }
1485
+
1486
+            // Now put these into the attributes array as core values
1487
+            $objarray = (array) $row;
1488
+            foreach ($objarray as $key => $value) {
1489
+                $this->attributes[$key] = $value;
1490
+            }
1491
+
1492
+            // guid needs to be an int  https://github.com/elgg/elgg/issues/4111
1493
+            $this->attributes['guid'] = (int) $this->attributes['guid'];
1494
+
1495
+            // for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1496
+            $this->attributes['subtype'] = (int) $this->attributes['subtype'];
1497
+
1498
+            // Cache object handle
1499
+            if ($this->attributes['guid']) {
1500
+                _elgg_services()->entityCache->set($this);
1501
+            }
1502
+
1503
+            return true;
1504
+        }
1505
+
1506
+        return false;
1507
+    }
1508
+
1509
+    /**
1510
+     * Stores non-attributes from the loading of the entity as volatile data
1511
+     *
1512
+     * @param array $data Key value array
1513
+     * @return void
1514
+     */
1515
+    protected function loadAdditionalSelectValues(array $data) {
1516
+        foreach ($data as $name => $value) {
1517
+            $this->setVolatileData("select:$name", $value);
1518
+        }
1519
+    }
1520 1520
 	
1521
-	/**
1522
-	 * Load new data from database into existing entity. Overwrites data but
1523
-	 * does not change values not included in the latest data.
1524
-	 *
1525
-	 * @internal This is used when the same entity is selected twice during a
1526
-	 * request in case different select clauses were used to load different data
1527
-	 * into volatile data.
1528
-	 *
1529
-	 * @param \stdClass $row DB row with new entity data
1530
-	 * @return bool
1531
-	 * @access private
1532
-	 */
1533
-	public function refresh(\stdClass $row) {
1534
-		if ($row instanceof \stdClass) {
1535
-			return $this->load($row);
1536
-		}
1537
-		return false;
1538
-	}
1539
-
1540
-	/**
1541
-	 * Disable this entity.
1542
-	 *
1543
-	 * Disabled entities are not returned by getter functions.
1544
-	 * To enable an entity, use {@link \ElggEntity::enable()}.
1545
-	 *
1546
-	 * Recursively disabling an entity will disable all entities
1547
-	 * owned or contained by the parent entity.
1548
-	 *
1549
-	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1550
-	 *
1551
-	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1552
-	 *
1553
-	 * @param string $reason    Optional reason
1554
-	 * @param bool   $recursive Recursively disable all contained entities?
1555
-	 *
1556
-	 * @return bool
1557
-	 * @see \ElggEntity::enable()
1558
-	 */
1559
-	public function disable($reason = "", $recursive = true) {
1560
-		if (!$this->guid) {
1561
-			return false;
1562
-		}
1521
+    /**
1522
+     * Load new data from database into existing entity. Overwrites data but
1523
+     * does not change values not included in the latest data.
1524
+     *
1525
+     * @internal This is used when the same entity is selected twice during a
1526
+     * request in case different select clauses were used to load different data
1527
+     * into volatile data.
1528
+     *
1529
+     * @param \stdClass $row DB row with new entity data
1530
+     * @return bool
1531
+     * @access private
1532
+     */
1533
+    public function refresh(\stdClass $row) {
1534
+        if ($row instanceof \stdClass) {
1535
+            return $this->load($row);
1536
+        }
1537
+        return false;
1538
+    }
1539
+
1540
+    /**
1541
+     * Disable this entity.
1542
+     *
1543
+     * Disabled entities are not returned by getter functions.
1544
+     * To enable an entity, use {@link \ElggEntity::enable()}.
1545
+     *
1546
+     * Recursively disabling an entity will disable all entities
1547
+     * owned or contained by the parent entity.
1548
+     *
1549
+     * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1550
+     *
1551
+     * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1552
+     *
1553
+     * @param string $reason    Optional reason
1554
+     * @param bool   $recursive Recursively disable all contained entities?
1555
+     *
1556
+     * @return bool
1557
+     * @see \ElggEntity::enable()
1558
+     */
1559
+    public function disable($reason = "", $recursive = true) {
1560
+        if (!$this->guid) {
1561
+            return false;
1562
+        }
1563 1563
 		
1564
-		if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
1565
-			return false;
1566
-		}
1564
+        if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
1565
+            return false;
1566
+        }
1567 1567
 		
1568
-		if (!$this->canEdit()) {
1569
-			return false;
1570
-		}
1571
-
1572
-		if ($this instanceof ElggUser && $this->banned === 'no') {
1573
-			// temporarily ban to prevent using the site during disable
1574
-			_elgg_services()->usersTable->markBanned($this->guid, true);
1575
-			$unban_after = true;
1576
-		} else {
1577
-			$unban_after = false;
1578
-		}
1579
-
1580
-		if ($reason) {
1581
-			$this->disable_reason = $reason;
1582
-		}
1583
-
1584
-		$dbprefix = elgg_get_config('dbprefix');
1568
+        if (!$this->canEdit()) {
1569
+            return false;
1570
+        }
1571
+
1572
+        if ($this instanceof ElggUser && $this->banned === 'no') {
1573
+            // temporarily ban to prevent using the site during disable
1574
+            _elgg_services()->usersTable->markBanned($this->guid, true);
1575
+            $unban_after = true;
1576
+        } else {
1577
+            $unban_after = false;
1578
+        }
1579
+
1580
+        if ($reason) {
1581
+            $this->disable_reason = $reason;
1582
+        }
1583
+
1584
+        $dbprefix = elgg_get_config('dbprefix');
1585 1585
 		
1586
-		$guid = (int) $this->guid;
1586
+        $guid = (int) $this->guid;
1587 1587
 		
1588
-		if ($recursive) {
1589
-			// Only disable enabled subentities
1590
-			$hidden = access_get_show_hidden_status();
1591
-			access_show_hidden_entities(false);
1592
-
1593
-			$ia = elgg_set_ignore_access(true);
1594
-
1595
-			$base_options = [
1596
-				'wheres' => [
1597
-					"e.guid != $guid",
1598
-				],
1599
-				'limit' => false,
1600
-			];
1588
+        if ($recursive) {
1589
+            // Only disable enabled subentities
1590
+            $hidden = access_get_show_hidden_status();
1591
+            access_show_hidden_entities(false);
1592
+
1593
+            $ia = elgg_set_ignore_access(true);
1594
+
1595
+            $base_options = [
1596
+                'wheres' => [
1597
+                    "e.guid != $guid",
1598
+                ],
1599
+                'limit' => false,
1600
+            ];
1601 1601
 			
1602
-			foreach (['owner_guid', 'container_guid'] as $db_column) {
1603
-				$options = $base_options;
1604
-				$options[$db_column] = $guid;
1602
+            foreach (['owner_guid', 'container_guid'] as $db_column) {
1603
+                $options = $base_options;
1604
+                $options[$db_column] = $guid;
1605 1605
 				
1606
-				$subentities = new \ElggBatch('elgg_get_entities', $options);
1607
-				$subentities->setIncrementOffset(false);
1606
+                $subentities = new \ElggBatch('elgg_get_entities', $options);
1607
+                $subentities->setIncrementOffset(false);
1608 1608
 				
1609
-				foreach ($subentities as $subentity) {
1610
-					/* @var $subentity \ElggEntity */
1611
-					if (!$subentity->isEnabled()) {
1612
-						continue;
1613
-					}
1614
-					add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1615
-					$subentity->disable($reason);
1616
-				}
1617
-			}
1618
-
1619
-			access_show_hidden_entities($hidden);
1620
-			elgg_set_ignore_access($ia);
1621
-		}
1622
-
1623
-		$this->disableMetadata();
1624
-		$this->disableAnnotations();
1625
-
1626
-		_elgg_services()->entityCache->remove($guid);
1609
+                foreach ($subentities as $subentity) {
1610
+                    /* @var $subentity \ElggEntity */
1611
+                    if (!$subentity->isEnabled()) {
1612
+                        continue;
1613
+                    }
1614
+                    add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1615
+                    $subentity->disable($reason);
1616
+                }
1617
+            }
1618
+
1619
+            access_show_hidden_entities($hidden);
1620
+            elgg_set_ignore_access($ia);
1621
+        }
1622
+
1623
+        $this->disableMetadata();
1624
+        $this->disableAnnotations();
1625
+
1626
+        _elgg_services()->entityCache->remove($guid);
1627 1627
 		
1628
-		$sql = "
1628
+        $sql = "
1629 1629
 			UPDATE {$dbprefix}entities
1630 1630
 			SET enabled = 'no'
1631 1631
 			WHERE guid = :guid
1632 1632
 		";
1633
-		$params = [
1634
-			':guid' => $guid,
1635
-		];
1636
-		$disabled = $this->getDatabase()->updateData($sql, false, $params);
1637
-
1638
-		if ($unban_after) {
1639
-			_elgg_services()->usersTable->markBanned($this->guid, false);
1640
-		}
1641
-
1642
-		if ($disabled) {
1643
-			$this->attributes['enabled'] = 'no';
1644
-			_elgg_services()->events->trigger('disable:after', $this->type, $this);
1645
-		}
1646
-
1647
-		return (bool) $disabled;
1648
-	}
1649
-
1650
-	/**
1651
-	 * Enable the entity
1652
-	 *
1653
-	 * @warning Disabled entities can't be loaded unless
1654
-	 * {@link access_show_hidden_entities(true)} has been called.
1655
-	 *
1656
-	 * @param bool $recursive Recursively enable all entities disabled with the entity?
1657
-	 * @see access_show_hiden_entities()
1658
-	 * @return bool
1659
-	 */
1660
-	public function enable($recursive = true) {
1661
-		$guid = (int) $this->guid;
1662
-		if (!$guid) {
1663
-			return false;
1664
-		}
1633
+        $params = [
1634
+            ':guid' => $guid,
1635
+        ];
1636
+        $disabled = $this->getDatabase()->updateData($sql, false, $params);
1637
+
1638
+        if ($unban_after) {
1639
+            _elgg_services()->usersTable->markBanned($this->guid, false);
1640
+        }
1641
+
1642
+        if ($disabled) {
1643
+            $this->attributes['enabled'] = 'no';
1644
+            _elgg_services()->events->trigger('disable:after', $this->type, $this);
1645
+        }
1646
+
1647
+        return (bool) $disabled;
1648
+    }
1649
+
1650
+    /**
1651
+     * Enable the entity
1652
+     *
1653
+     * @warning Disabled entities can't be loaded unless
1654
+     * {@link access_show_hidden_entities(true)} has been called.
1655
+     *
1656
+     * @param bool $recursive Recursively enable all entities disabled with the entity?
1657
+     * @see access_show_hiden_entities()
1658
+     * @return bool
1659
+     */
1660
+    public function enable($recursive = true) {
1661
+        $guid = (int) $this->guid;
1662
+        if (!$guid) {
1663
+            return false;
1664
+        }
1665 1665
 		
1666
-		if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
1667
-			return false;
1668
-		}
1666
+        if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
1667
+            return false;
1668
+        }
1669 1669
 		
1670
-		if (!$this->canEdit()) {
1671
-			return false;
1672
-		}
1670
+        if (!$this->canEdit()) {
1671
+            return false;
1672
+        }
1673 1673
 		
1674
-		global $CONFIG;
1674
+        global $CONFIG;
1675 1675
 	
1676
-		// Override access only visible entities
1677
-		$old_access_status = access_get_show_hidden_status();
1678
-		access_show_hidden_entities(true);
1676
+        // Override access only visible entities
1677
+        $old_access_status = access_get_show_hidden_status();
1678
+        access_show_hidden_entities(true);
1679 1679
 	
1680
-		$result = $this->getDatabase()->updateData("UPDATE {$CONFIG->dbprefix}entities
1680
+        $result = $this->getDatabase()->updateData("UPDATE {$CONFIG->dbprefix}entities
1681 1681
 			SET enabled = 'yes'
1682 1682
 			WHERE guid = $guid");
1683 1683
 
1684
-		$this->deleteMetadata('disable_reason');
1685
-		$this->enableMetadata();
1686
-		$this->enableAnnotations();
1687
-
1688
-		if ($recursive) {
1689
-			$disabled_with_it = elgg_get_entities_from_relationship([
1690
-				'relationship' => 'disabled_with',
1691
-				'relationship_guid' => $guid,
1692
-				'inverse_relationship' => true,
1693
-				'limit' => 0,
1694
-			]);
1695
-
1696
-			foreach ($disabled_with_it as $e) {
1697
-				$e->enable();
1698
-				remove_entity_relationship($e->guid, 'disabled_with', $guid);
1699
-			}
1700
-		}
1684
+        $this->deleteMetadata('disable_reason');
1685
+        $this->enableMetadata();
1686
+        $this->enableAnnotations();
1687
+
1688
+        if ($recursive) {
1689
+            $disabled_with_it = elgg_get_entities_from_relationship([
1690
+                'relationship' => 'disabled_with',
1691
+                'relationship_guid' => $guid,
1692
+                'inverse_relationship' => true,
1693
+                'limit' => 0,
1694
+            ]);
1695
+
1696
+            foreach ($disabled_with_it as $e) {
1697
+                $e->enable();
1698
+                remove_entity_relationship($e->guid, 'disabled_with', $guid);
1699
+            }
1700
+        }
1701 1701
 	
1702
-		access_show_hidden_entities($old_access_status);
1702
+        access_show_hidden_entities($old_access_status);
1703 1703
 	
1704
-		if ($result) {
1705
-			$this->attributes['enabled'] = 'yes';
1706
-			_elgg_services()->events->trigger('enable:after', $this->type, $this);
1707
-		}
1708
-
1709
-		return $result;
1710
-	}
1711
-
1712
-	/**
1713
-	 * Is this entity enabled?
1714
-	 *
1715
-	 * @return boolean Whether this entity is enabled.
1716
-	 */
1717
-	public function isEnabled() {
1718
-		return $this->enabled == 'yes';
1719
-	}
1720
-
1721
-	/**
1722
-	 * Deletes the entity.
1723
-	 *
1724
-	 * Removes the entity and its metadata, annotations, relationships,
1725
-	 * river entries, and private data.
1726
-	 *
1727
-	 * Optionally can remove entities contained and owned by this entity.
1728
-	 *
1729
-	 * @warning If deleting recursively, this bypasses ownership of items contained by
1730
-	 * the entity.  That means that if the container_guid = $this->guid, the item will
1731
-	 * be deleted regardless of who owns it.
1732
-	 *
1733
-	 * @param bool $recursive If true (default) then all entities which are
1734
-	 *                        owned or contained by $this will also be deleted.
1735
-	 *
1736
-	 * @return bool
1737
-	 */
1738
-	public function delete($recursive = true) {
1739
-
1740
-		$guid = $this->guid;
1741
-		if (!$guid) {
1742
-			return false;
1743
-		}
1704
+        if ($result) {
1705
+            $this->attributes['enabled'] = 'yes';
1706
+            _elgg_services()->events->trigger('enable:after', $this->type, $this);
1707
+        }
1708
+
1709
+        return $result;
1710
+    }
1711
+
1712
+    /**
1713
+     * Is this entity enabled?
1714
+     *
1715
+     * @return boolean Whether this entity is enabled.
1716
+     */
1717
+    public function isEnabled() {
1718
+        return $this->enabled == 'yes';
1719
+    }
1720
+
1721
+    /**
1722
+     * Deletes the entity.
1723
+     *
1724
+     * Removes the entity and its metadata, annotations, relationships,
1725
+     * river entries, and private data.
1726
+     *
1727
+     * Optionally can remove entities contained and owned by this entity.
1728
+     *
1729
+     * @warning If deleting recursively, this bypasses ownership of items contained by
1730
+     * the entity.  That means that if the container_guid = $this->guid, the item will
1731
+     * be deleted regardless of who owns it.
1732
+     *
1733
+     * @param bool $recursive If true (default) then all entities which are
1734
+     *                        owned or contained by $this will also be deleted.
1735
+     *
1736
+     * @return bool
1737
+     */
1738
+    public function delete($recursive = true) {
1739
+
1740
+        $guid = $this->guid;
1741
+        if (!$guid) {
1742
+            return false;
1743
+        }
1744 1744
 		
1745
-		// first check if we can delete this entity
1746
-		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
1747
-		// which could potentially remove some content if the user didn't have access
1748
-		if (!$this->canDelete()) {
1749
-			return false;
1750
-		}
1751
-
1752
-		// now trigger an event to let others know this entity is about to be deleted
1753
-		// so they can prevent it or take their own actions
1754
-		if (!_elgg_services()->events->trigger('delete', $this->type, $this)) {
1755
-			return false;
1756
-		}
1757
-
1758
-		if ($this instanceof ElggUser) {
1759
-			// ban to prevent using the site during delete
1760
-			_elgg_services()->usersTable->markBanned($this->guid, true);
1761
-		}
1762
-
1763
-		// Delete contained owned and otherwise releated objects (depth first)
1764
-		if ($recursive) {
1765
-			// Temporarily overriding access controls
1766
-			$entity_disable_override = access_get_show_hidden_status();
1767
-			access_show_hidden_entities(true);
1768
-			$ia = elgg_set_ignore_access(true);
1769
-
1770
-			// @todo there was logic in the original code that ignored
1771
-			// entities with owner or container guids of themselves.
1772
-			// this should probably be prevented in \ElggEntity instead of checked for here
1773
-			$base_options = [
1774
-				'wheres' => [
1775
-					"e.guid != $guid",
1776
-				],
1777
-				'limit' => false,
1778
-			];
1745
+        // first check if we can delete this entity
1746
+        // NOTE: in Elgg <= 1.10.3 this was after the delete event,
1747
+        // which could potentially remove some content if the user didn't have access
1748
+        if (!$this->canDelete()) {
1749
+            return false;
1750
+        }
1751
+
1752
+        // now trigger an event to let others know this entity is about to be deleted
1753
+        // so they can prevent it or take their own actions
1754
+        if (!_elgg_services()->events->trigger('delete', $this->type, $this)) {
1755
+            return false;
1756
+        }
1757
+
1758
+        if ($this instanceof ElggUser) {
1759
+            // ban to prevent using the site during delete
1760
+            _elgg_services()->usersTable->markBanned($this->guid, true);
1761
+        }
1762
+
1763
+        // Delete contained owned and otherwise releated objects (depth first)
1764
+        if ($recursive) {
1765
+            // Temporarily overriding access controls
1766
+            $entity_disable_override = access_get_show_hidden_status();
1767
+            access_show_hidden_entities(true);
1768
+            $ia = elgg_set_ignore_access(true);
1769
+
1770
+            // @todo there was logic in the original code that ignored
1771
+            // entities with owner or container guids of themselves.
1772
+            // this should probably be prevented in \ElggEntity instead of checked for here
1773
+            $base_options = [
1774
+                'wheres' => [
1775
+                    "e.guid != $guid",
1776
+                ],
1777
+                'limit' => false,
1778
+            ];
1779 1779
 			
1780
-			foreach (['owner_guid', 'container_guid'] as $db_column) {
1781
-				$options = $base_options;
1782
-				$options[$db_column] = $guid;
1780
+            foreach (['owner_guid', 'container_guid'] as $db_column) {
1781
+                $options = $base_options;
1782
+                $options[$db_column] = $guid;
1783 1783
 				
1784
-				$batch = new \ElggBatch('elgg_get_entities', $options);
1785
-				$batch->setIncrementOffset(false);
1784
+                $batch = new \ElggBatch('elgg_get_entities', $options);
1785
+                $batch->setIncrementOffset(false);
1786 1786
 				
1787
-				/* @var $e \ElggEntity */
1788
-				foreach ($batch as $e) {
1789
-					$e->delete(true);
1790
-				}
1791
-			}
1787
+                /* @var $e \ElggEntity */
1788
+                foreach ($batch as $e) {
1789
+                    $e->delete(true);
1790
+                }
1791
+            }
1792 1792
 			
1793
-			access_show_hidden_entities($entity_disable_override);
1794
-			elgg_set_ignore_access($ia);
1795
-		}
1793
+            access_show_hidden_entities($entity_disable_override);
1794
+            elgg_set_ignore_access($ia);
1795
+        }
1796 1796
 
1797
-		$entity_disable_override = access_get_show_hidden_status();
1798
-		access_show_hidden_entities(true);
1799
-		$ia = elgg_set_ignore_access(true);
1797
+        $entity_disable_override = access_get_show_hidden_status();
1798
+        access_show_hidden_entities(true);
1799
+        $ia = elgg_set_ignore_access(true);
1800 1800
 		
1801
-		// Now delete the entity itself
1802
-		$this->deleteMetadata();
1803
-		$this->deleteOwnedMetadata();
1804
-		$this->deleteAnnotations();
1805
-		$this->deleteOwnedAnnotations();
1806
-		$this->deleteRelationships();
1807
-		$this->deleteAccessCollectionMemberships();
1808
-		$this->deleteOwnedAccessCollections();
1809
-
1810
-		access_show_hidden_entities($entity_disable_override);
1811
-		elgg_set_ignore_access($ia);
1812
-
1813
-		_elgg_delete_river(['subject_guid' => $guid]);
1814
-		_elgg_delete_river(['object_guid' => $guid]);
1815
-		_elgg_delete_river(['target_guid' => $guid]);
1816
-		remove_all_private_settings($guid);
1817
-
1818
-		_elgg_invalidate_cache_for_entity($guid);
1819
-		_elgg_invalidate_memcache_for_entity($guid);
1820
-
1821
-		$dbprefix = elgg_get_config('dbprefix');
1801
+        // Now delete the entity itself
1802
+        $this->deleteMetadata();
1803
+        $this->deleteOwnedMetadata();
1804
+        $this->deleteAnnotations();
1805
+        $this->deleteOwnedAnnotations();
1806
+        $this->deleteRelationships();
1807
+        $this->deleteAccessCollectionMemberships();
1808
+        $this->deleteOwnedAccessCollections();
1809
+
1810
+        access_show_hidden_entities($entity_disable_override);
1811
+        elgg_set_ignore_access($ia);
1812
+
1813
+        _elgg_delete_river(['subject_guid' => $guid]);
1814
+        _elgg_delete_river(['object_guid' => $guid]);
1815
+        _elgg_delete_river(['target_guid' => $guid]);
1816
+        remove_all_private_settings($guid);
1817
+
1818
+        _elgg_invalidate_cache_for_entity($guid);
1819
+        _elgg_invalidate_memcache_for_entity($guid);
1820
+
1821
+        $dbprefix = elgg_get_config('dbprefix');
1822 1822
 		
1823
-		$sql = "
1823
+        $sql = "
1824 1824
 			DELETE FROM {$dbprefix}entities
1825 1825
 			WHERE guid = :guid
1826 1826
 		";
1827
-		$params = [
1828
-			':guid' => $guid,
1829
-		];
1827
+        $params = [
1828
+            ':guid' => $guid,
1829
+        ];
1830 1830
 
1831
-		$deleted = $this->getDatabase()->deleteData($sql, $params);
1831
+        $deleted = $this->getDatabase()->deleteData($sql, $params);
1832 1832
 
1833
-		if ($deleted && in_array($this->type, ['object', 'user', 'group', 'site'])) {
1834
-			// delete from type-specific subtable
1835
-			$sql = "
1833
+        if ($deleted && in_array($this->type, ['object', 'user', 'group', 'site'])) {
1834
+            // delete from type-specific subtable
1835
+            $sql = "
1836 1836
 				DELETE FROM {$dbprefix}{$this->type}s_entity
1837 1837
 				WHERE guid = :guid
1838 1838
 			";
1839
-			$this->getDatabase()->deleteData($sql, $params);
1840
-		}
1839
+            $this->getDatabase()->deleteData($sql, $params);
1840
+        }
1841 1841
 		
1842
-		_elgg_clear_entity_files($this);
1843
-
1844
-		return (bool) $deleted;
1845
-	}
1846
-
1847
-	/**
1848
-	 * {@inheritdoc}
1849
-	 */
1850
-	public function toObject() {
1851
-		$object = $this->prepareObject(new \stdClass());
1852
-		$params = ['entity' => $this];
1853
-		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1854
-		return $object;
1855
-	}
1856
-
1857
-	/**
1858
-	 * Prepare an object copy for toObject()
1859
-	 *
1860
-	 * @param \stdClass $object Object representation of the entity
1861
-	 * @return \stdClass
1862
-	 */
1863
-	protected function prepareObject($object) {
1864
-		$object->guid = $this->guid;
1865
-		$object->type = $this->getType();
1866
-		$object->subtype = $this->getSubtype();
1867
-		$object->owner_guid = $this->getOwnerGUID();
1868
-		$object->container_guid = $this->getContainerGUID();
1869
-		$object->time_created = date('c', $this->getTimeCreated());
1870
-		$object->time_updated = date('c', $this->getTimeUpdated());
1871
-		$object->url = $this->getURL();
1872
-		$object->read_access = (int) $this->access_id;
1873
-		return $object;
1874
-	}
1875
-
1876
-	/*
1842
+        _elgg_clear_entity_files($this);
1843
+
1844
+        return (bool) $deleted;
1845
+    }
1846
+
1847
+    /**
1848
+     * {@inheritdoc}
1849
+     */
1850
+    public function toObject() {
1851
+        $object = $this->prepareObject(new \stdClass());
1852
+        $params = ['entity' => $this];
1853
+        $object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1854
+        return $object;
1855
+    }
1856
+
1857
+    /**
1858
+     * Prepare an object copy for toObject()
1859
+     *
1860
+     * @param \stdClass $object Object representation of the entity
1861
+     * @return \stdClass
1862
+     */
1863
+    protected function prepareObject($object) {
1864
+        $object->guid = $this->guid;
1865
+        $object->type = $this->getType();
1866
+        $object->subtype = $this->getSubtype();
1867
+        $object->owner_guid = $this->getOwnerGUID();
1868
+        $object->container_guid = $this->getContainerGUID();
1869
+        $object->time_created = date('c', $this->getTimeCreated());
1870
+        $object->time_updated = date('c', $this->getTimeUpdated());
1871
+        $object->url = $this->getURL();
1872
+        $object->read_access = (int) $this->access_id;
1873
+        return $object;
1874
+    }
1875
+
1876
+    /*
1877 1877
 	 * LOCATABLE INTERFACE
1878 1878
 	 */
1879 1879
 
1880
-	/**
1881
-	 * Gets the 'location' metadata for the entity
1882
-	 *
1883
-	 * @return string The location
1884
-	 */
1885
-	public function getLocation() {
1886
-		return $this->location;
1887
-	}
1888
-
1889
-	/**
1890
-	 * Sets the 'location' metadata for the entity
1891
-	 *
1892
-	 * @param string $location String representation of the location
1893
-	 *
1894
-	 * @return void
1895
-	 */
1896
-	public function setLocation($location) {
1897
-		$this->location = $location;
1898
-	}
1899
-
1900
-	/**
1901
-	 * Set latitude and longitude metadata tags for a given entity.
1902
-	 *
1903
-	 * @param float $lat  Latitude
1904
-	 * @param float $long Longitude
1905
-	 *
1906
-	 * @return void
1907
-	 * @todo Unimplemented
1908
-	 */
1909
-	public function setLatLong($lat, $long) {
1910
-		$this->{"geo:lat"} = $lat;
1911
-		$this->{"geo:long"} = $long;
1912
-	}
1913
-
1914
-	/**
1915
-	 * Return the entity's latitude.
1916
-	 *
1917
-	 * @return float
1918
-	 * @todo Unimplemented
1919
-	 */
1920
-	public function getLatitude() {
1921
-		return (float) $this->{"geo:lat"};
1922
-	}
1923
-
1924
-	/**
1925
-	 * Return the entity's longitude
1926
-	 *
1927
-	 * @return float
1928
-	 * @todo Unimplemented
1929
-	 */
1930
-	public function getLongitude() {
1931
-		return (float) $this->{"geo:long"};
1932
-	}
1933
-
1934
-	/*
1880
+    /**
1881
+     * Gets the 'location' metadata for the entity
1882
+     *
1883
+     * @return string The location
1884
+     */
1885
+    public function getLocation() {
1886
+        return $this->location;
1887
+    }
1888
+
1889
+    /**
1890
+     * Sets the 'location' metadata for the entity
1891
+     *
1892
+     * @param string $location String representation of the location
1893
+     *
1894
+     * @return void
1895
+     */
1896
+    public function setLocation($location) {
1897
+        $this->location = $location;
1898
+    }
1899
+
1900
+    /**
1901
+     * Set latitude and longitude metadata tags for a given entity.
1902
+     *
1903
+     * @param float $lat  Latitude
1904
+     * @param float $long Longitude
1905
+     *
1906
+     * @return void
1907
+     * @todo Unimplemented
1908
+     */
1909
+    public function setLatLong($lat, $long) {
1910
+        $this->{"geo:lat"} = $lat;
1911
+        $this->{"geo:long"} = $long;
1912
+    }
1913
+
1914
+    /**
1915
+     * Return the entity's latitude.
1916
+     *
1917
+     * @return float
1918
+     * @todo Unimplemented
1919
+     */
1920
+    public function getLatitude() {
1921
+        return (float) $this->{"geo:lat"};
1922
+    }
1923
+
1924
+    /**
1925
+     * Return the entity's longitude
1926
+     *
1927
+     * @return float
1928
+     * @todo Unimplemented
1929
+     */
1930
+    public function getLongitude() {
1931
+        return (float) $this->{"geo:long"};
1932
+    }
1933
+
1934
+    /*
1935 1935
 	 * SYSTEM LOG INTERFACE
1936 1936
 	 */
1937 1937
 
1938
-	/**
1939
-	 * Return an identification for the object for storage in the system log.
1940
-	 * This id must be an integer.
1941
-	 *
1942
-	 * @return int
1943
-	 */
1944
-	public function getSystemLogID() {
1945
-		return $this->getGUID();
1946
-	}
1947
-
1948
-	/**
1949
-	 * For a given ID, return the object associated with it.
1950
-	 * This is used by the system log. It can be called on any Loggable object.
1951
-	 *
1952
-	 * @param int $id GUID.
1953
-	 * @return int GUID
1954
-	 */
1955
-	public function getObjectFromID($id) {
1956
-		return get_entity($id);
1957
-	}
1958
-
1959
-	/**
1960
-	 * Returns tags for this entity.
1961
-	 *
1962
-	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
1963
-	 *
1964
-	 * @param array $tag_names Optionally restrict by tag metadata names.
1965
-	 *
1966
-	 * @return array
1967
-	 */
1968
-	public function getTags($tag_names = null) {
1969
-		if ($tag_names && !is_array($tag_names)) {
1970
-			$tag_names = [$tag_names];
1971
-		}
1972
-
1973
-		$valid_tags = elgg_get_registered_tag_metadata_names();
1974
-		$entity_tags = [];
1975
-
1976
-		foreach ($valid_tags as $tag_name) {
1977
-			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
1978
-				continue;
1979
-			}
1980
-
1981
-			if ($tags = $this->$tag_name) {
1982
-				// if a single tag, metadata returns a string.
1983
-				// if multiple tags, metadata returns an array.
1984
-				if (is_array($tags)) {
1985
-					$entity_tags = array_merge($entity_tags, $tags);
1986
-				} else {
1987
-					$entity_tags[] = $tags;
1988
-				}
1989
-			}
1990
-		}
1991
-
1992
-		return $entity_tags;
1993
-	}
1938
+    /**
1939
+     * Return an identification for the object for storage in the system log.
1940
+     * This id must be an integer.
1941
+     *
1942
+     * @return int
1943
+     */
1944
+    public function getSystemLogID() {
1945
+        return $this->getGUID();
1946
+    }
1947
+
1948
+    /**
1949
+     * For a given ID, return the object associated with it.
1950
+     * This is used by the system log. It can be called on any Loggable object.
1951
+     *
1952
+     * @param int $id GUID.
1953
+     * @return int GUID
1954
+     */
1955
+    public function getObjectFromID($id) {
1956
+        return get_entity($id);
1957
+    }
1958
+
1959
+    /**
1960
+     * Returns tags for this entity.
1961
+     *
1962
+     * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
1963
+     *
1964
+     * @param array $tag_names Optionally restrict by tag metadata names.
1965
+     *
1966
+     * @return array
1967
+     */
1968
+    public function getTags($tag_names = null) {
1969
+        if ($tag_names && !is_array($tag_names)) {
1970
+            $tag_names = [$tag_names];
1971
+        }
1972
+
1973
+        $valid_tags = elgg_get_registered_tag_metadata_names();
1974
+        $entity_tags = [];
1975
+
1976
+        foreach ($valid_tags as $tag_name) {
1977
+            if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
1978
+                continue;
1979
+            }
1980
+
1981
+            if ($tags = $this->$tag_name) {
1982
+                // if a single tag, metadata returns a string.
1983
+                // if multiple tags, metadata returns an array.
1984
+                if (is_array($tags)) {
1985
+                    $entity_tags = array_merge($entity_tags, $tags);
1986
+                } else {
1987
+                    $entity_tags[] = $tags;
1988
+                }
1989
+            }
1990
+        }
1991
+
1992
+        return $entity_tags;
1993
+    }
1994 1994
 	
1995
-	/**
1996
-	 * Remove the membership of all access collections for this entity (if the entity is a user)
1997
-	 *
1998
-	 * @return bool
1999
-	 * @since 1.11
2000
-	 */
2001
-	public function deleteAccessCollectionMemberships() {
1995
+    /**
1996
+     * Remove the membership of all access collections for this entity (if the entity is a user)
1997
+     *
1998
+     * @return bool
1999
+     * @since 1.11
2000
+     */
2001
+    public function deleteAccessCollectionMemberships() {
2002 2002
 	
2003
-		if (!$this->guid) {
2004
-			return false;
2005
-		}
2003
+        if (!$this->guid) {
2004
+            return false;
2005
+        }
2006 2006
 		
2007
-		if ($this->type !== 'user') {
2008
-			return true;
2009
-		}
2007
+        if ($this->type !== 'user') {
2008
+            return true;
2009
+        }
2010 2010
 		
2011
-		$ac = _elgg_services()->accessCollections;
2011
+        $ac = _elgg_services()->accessCollections;
2012 2012
 		
2013
-		$collections = $ac->getCollectionsByMember($this->guid);
2014
-		if (empty($collections)) {
2015
-			return true;
2016
-		}
2013
+        $collections = $ac->getCollectionsByMember($this->guid);
2014
+        if (empty($collections)) {
2015
+            return true;
2016
+        }
2017 2017
 		
2018
-		$result = true;
2019
-		foreach ($collections as $collection) {
2020
-			$result = $result & $ac->removeUser($this->guid, $collection->id);
2021
-		}
2018
+        $result = true;
2019
+        foreach ($collections as $collection) {
2020
+            $result = $result & $ac->removeUser($this->guid, $collection->id);
2021
+        }
2022 2022
 		
2023
-		return $result;
2024
-	}
2023
+        return $result;
2024
+    }
2025 2025
 	
2026
-	/**
2027
-	 * Remove all access collections owned by this entity
2028
-	 *
2029
-	 * @return bool
2030
-	 * @since 1.11
2031
-	 */
2032
-	public function deleteOwnedAccessCollections() {
2026
+    /**
2027
+     * Remove all access collections owned by this entity
2028
+     *
2029
+     * @return bool
2030
+     * @since 1.11
2031
+     */
2032
+    public function deleteOwnedAccessCollections() {
2033 2033
 		
2034
-		if (!$this->guid) {
2035
-			return false;
2036
-		}
2034
+        if (!$this->guid) {
2035
+            return false;
2036
+        }
2037 2037
 		
2038
-		$ac = _elgg_services()->accessCollections;
2038
+        $ac = _elgg_services()->accessCollections;
2039 2039
 		
2040
-		$collections = $ac->getEntityCollections($this->guid);
2041
-		if (empty($collections)) {
2042
-			return true;
2043
-		}
2040
+        $collections = $ac->getEntityCollections($this->guid);
2041
+        if (empty($collections)) {
2042
+            return true;
2043
+        }
2044 2044
 		
2045
-		$result = true;
2046
-		foreach ($collections as $collection) {
2047
-			$result = $result & $ac->delete($collection->id);
2048
-		}
2045
+        $result = true;
2046
+        foreach ($collections as $collection) {
2047
+            $result = $result & $ac->delete($collection->id);
2048
+        }
2049 2049
 		
2050
-		return $result;
2051
-	}
2052
-
2053
-	/**
2054
-	 * Update the last_action column in the entities table.
2055
-	 *
2056
-	 * @warning This is different to time_updated.  Time_updated is automatically set,
2057
-	 * while last_action is only set when explicitly called.
2058
-	 *
2059
-	 * @param int $posted Timestamp of last action
2060
-	 * @return int|false
2061
-	 * @access private
2062
-	 */
2063
-	public function updateLastAction($posted = null) {
2064
-		$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2065
-		if ($posted) {
2066
-			$this->attributes['last_action'] = $posted;
2067
-			_elgg_services()->entityCache->set($this);
2068
-			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
2069
-		}
2070
-		return $posted;
2071
-	}
2050
+        return $result;
2051
+    }
2052
+
2053
+    /**
2054
+     * Update the last_action column in the entities table.
2055
+     *
2056
+     * @warning This is different to time_updated.  Time_updated is automatically set,
2057
+     * while last_action is only set when explicitly called.
2058
+     *
2059
+     * @param int $posted Timestamp of last action
2060
+     * @return int|false
2061
+     * @access private
2062
+     */
2063
+    public function updateLastAction($posted = null) {
2064
+        $posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2065
+        if ($posted) {
2066
+            $this->attributes['last_action'] = $posted;
2067
+            _elgg_services()->entityCache->set($this);
2068
+            $this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
2069
+        }
2070
+        return $posted;
2071
+    }
2072 2072
 }
Please login to merge, or discard this patch.
engine/classes/Elgg/Cache/EntityCache.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -11,164 +11,164 @@
 block discarded – undo
11 11
  */
12 12
 class EntityCache {
13 13
 
14
-	// @todo Pick a less arbitrary limit
15
-	const MAX_SIZE = 256;
16
-
17
-	/**
18
-	 * @var ElggEntity[] GUID keys
19
-	 */
20
-	private $entities = [];
21
-
22
-	/**
23
-	 * @var bool[] GUID keys
24
-	 */
25
-	private $disabled_guids = [];
26
-
27
-	/**
28
-	 * @var ElggSession
29
-	 */
30
-	private $session;
31
-
32
-	/**
33
-	 * @var MetadataCache
34
-	 */
35
-	private $metadata_cache;
36
-
37
-	/**
38
-	 * @var array
39
-	 */
40
-	private $username_cache = [];
14
+    // @todo Pick a less arbitrary limit
15
+    const MAX_SIZE = 256;
16
+
17
+    /**
18
+     * @var ElggEntity[] GUID keys
19
+     */
20
+    private $entities = [];
21
+
22
+    /**
23
+     * @var bool[] GUID keys
24
+     */
25
+    private $disabled_guids = [];
26
+
27
+    /**
28
+     * @var ElggSession
29
+     */
30
+    private $session;
31
+
32
+    /**
33
+     * @var MetadataCache
34
+     */
35
+    private $metadata_cache;
36
+
37
+    /**
38
+     * @var array
39
+     */
40
+    private $username_cache = [];
41 41
 	
42
-	/**
43
-	 * Constructor
44
-	 *
45
-	 * @param ElggSession   $session        Session
46
-	 * @param MetadataCache $metadata_cache MD cache
47
-	 */
48
-	public function __construct(ElggSession $session, MetadataCache $metadata_cache) {
49
-		$this->session = $session;
50
-		$this->metadata_cache = $metadata_cache;
51
-	}
52
-
53
-	/**
54
-	 * Retrieve a entity from the cache.
55
-	 *
56
-	 * @param int $guid The GUID
57
-	 *
58
-	 * @return \ElggEntity|false false if entity not cached
59
-	 */
60
-	public function get($guid) {
61
-		$guid = (int) $guid;
62
-
63
-		if (isset($this->entities[$guid])) {
64
-			return $this->entities[$guid];
65
-		}
66
-
67
-		return false;
68
-	}
69
-
70
-	/**
71
-	 * Returns cached user entity by username
72
-	 *
73
-	 * @param string $username Username
74
-	 * @return \ElggUser|false
75
-	 */
76
-	public function getByUsername($username) {
77
-		if (isset($this->username_cache[$username])) {
78
-			return $this->get($this->username_cache[$username]);
79
-		}
80
-		return false;
81
-	}
82
-
83
-	/**
84
-	 * Cache an entity.
85
-	 *
86
-	 * @param ElggEntity $entity Entity to cache
87
-	 * @return void
88
-	 */
89
-	public function set(ElggEntity $entity) {
90
-		$guid = $entity->guid;
91
-
92
-		if (!$guid || isset($this->entities[$guid]) || isset($this->disabled_guids[$guid])) {
93
-			// have it or not saved
94
-			return;
95
-		}
96
-
97
-		// Don't cache non-plugin entities while access control is off, otherwise they could be
98
-		// exposed to users who shouldn't see them when control is re-enabled.
99
-		if (!($entity instanceof \ElggPlugin) && $this->session->getIgnoreAccess()) {
100
-			return;
101
-		}
102
-
103
-		// Don't store too many or we'll have memory problems
104
-		if (count($this->entities) > self::MAX_SIZE) {
105
-			$this->remove(array_rand($this->entities));
106
-		}
107
-
108
-		$this->entities[$guid] = $entity;
109
-
110
-		if ($entity instanceof \ElggUser) {
111
-			$this->username_cache[$entity->username] = $entity->guid;
112
-		}
113
-	}
114
-
115
-	/**
116
-	 * Invalidate this class's entry in the cache.
117
-	 *
118
-	 * @param int $guid The entity guid
119
-	 * @return void
120
-	 */
121
-	public function remove($guid) {
122
-		$guid = (int) $guid;
42
+    /**
43
+     * Constructor
44
+     *
45
+     * @param ElggSession   $session        Session
46
+     * @param MetadataCache $metadata_cache MD cache
47
+     */
48
+    public function __construct(ElggSession $session, MetadataCache $metadata_cache) {
49
+        $this->session = $session;
50
+        $this->metadata_cache = $metadata_cache;
51
+    }
52
+
53
+    /**
54
+     * Retrieve a entity from the cache.
55
+     *
56
+     * @param int $guid The GUID
57
+     *
58
+     * @return \ElggEntity|false false if entity not cached
59
+     */
60
+    public function get($guid) {
61
+        $guid = (int) $guid;
62
+
63
+        if (isset($this->entities[$guid])) {
64
+            return $this->entities[$guid];
65
+        }
66
+
67
+        return false;
68
+    }
69
+
70
+    /**
71
+     * Returns cached user entity by username
72
+     *
73
+     * @param string $username Username
74
+     * @return \ElggUser|false
75
+     */
76
+    public function getByUsername($username) {
77
+        if (isset($this->username_cache[$username])) {
78
+            return $this->get($this->username_cache[$username]);
79
+        }
80
+        return false;
81
+    }
82
+
83
+    /**
84
+     * Cache an entity.
85
+     *
86
+     * @param ElggEntity $entity Entity to cache
87
+     * @return void
88
+     */
89
+    public function set(ElggEntity $entity) {
90
+        $guid = $entity->guid;
91
+
92
+        if (!$guid || isset($this->entities[$guid]) || isset($this->disabled_guids[$guid])) {
93
+            // have it or not saved
94
+            return;
95
+        }
96
+
97
+        // Don't cache non-plugin entities while access control is off, otherwise they could be
98
+        // exposed to users who shouldn't see them when control is re-enabled.
99
+        if (!($entity instanceof \ElggPlugin) && $this->session->getIgnoreAccess()) {
100
+            return;
101
+        }
102
+
103
+        // Don't store too many or we'll have memory problems
104
+        if (count($this->entities) > self::MAX_SIZE) {
105
+            $this->remove(array_rand($this->entities));
106
+        }
107
+
108
+        $this->entities[$guid] = $entity;
109
+
110
+        if ($entity instanceof \ElggUser) {
111
+            $this->username_cache[$entity->username] = $entity->guid;
112
+        }
113
+    }
114
+
115
+    /**
116
+     * Invalidate this class's entry in the cache.
117
+     *
118
+     * @param int $guid The entity guid
119
+     * @return void
120
+     */
121
+    public function remove($guid) {
122
+        $guid = (int) $guid;
123 123
 		
124
-		if (!isset($this->entities[$guid])) {
125
-			return;
126
-		}
127
-
128
-		unset($this->entities[$guid]);
129
-
130
-		$username = array_search($guid, $this->username_cache);
131
-		if ($username !== false) {
132
-			unset($this->username_cache[$username]);
133
-		}
134
-
135
-		// Purge separate metadata cache. Original idea was to do in entity destructor, but that would
136
-		// have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way
137
-		// to know that the expunged entity will be GCed (might be another reference living), but that's
138
-		// OK; the metadata will reload if necessary.
139
-		$this->metadata_cache->clear($guid);
140
-	}
141
-
142
-	/**
143
-	 * Clear the entity cache
144
-	 *
145
-	 * @return void
146
-	 */
147
-	public function clear() {
148
-		$this->entities = [];
149
-		$this->username_cache = [];
150
-	}
151
-
152
-	/**
153
-	 * Remove this entity from the entity cache and make sure it is not re-added
154
-	 *
155
-	 * @todo this is a workaround until #5604 can be implemented
156
-	 *
157
-	 * @param int $guid The entity guid
158
-	 * @return void
159
-	 */
160
-	public function disableCachingForEntity($guid) {
161
-		$this->remove($guid);
162
-		$this->disabled_guids[$guid] = true;
163
-	}
164
-
165
-	/**
166
-	 * Allow this entity to be stored in the entity cache
167
-	 *
168
-	 * @param int $guid The entity guid
169
-	 * @return void
170
-	 */
171
-	public function enableCachingForEntity($guid) {
172
-		unset($this->disabled_guids[$guid]);
173
-	}
124
+        if (!isset($this->entities[$guid])) {
125
+            return;
126
+        }
127
+
128
+        unset($this->entities[$guid]);
129
+
130
+        $username = array_search($guid, $this->username_cache);
131
+        if ($username !== false) {
132
+            unset($this->username_cache[$username]);
133
+        }
134
+
135
+        // Purge separate metadata cache. Original idea was to do in entity destructor, but that would
136
+        // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way
137
+        // to know that the expunged entity will be GCed (might be another reference living), but that's
138
+        // OK; the metadata will reload if necessary.
139
+        $this->metadata_cache->clear($guid);
140
+    }
141
+
142
+    /**
143
+     * Clear the entity cache
144
+     *
145
+     * @return void
146
+     */
147
+    public function clear() {
148
+        $this->entities = [];
149
+        $this->username_cache = [];
150
+    }
151
+
152
+    /**
153
+     * Remove this entity from the entity cache and make sure it is not re-added
154
+     *
155
+     * @todo this is a workaround until #5604 can be implemented
156
+     *
157
+     * @param int $guid The entity guid
158
+     * @return void
159
+     */
160
+    public function disableCachingForEntity($guid) {
161
+        $this->remove($guid);
162
+        $this->disabled_guids[$guid] = true;
163
+    }
164
+
165
+    /**
166
+     * Allow this entity to be stored in the entity cache
167
+     *
168
+     * @param int $guid The entity guid
169
+     * @return void
170
+     */
171
+    public function enableCachingForEntity($guid) {
172
+        unset($this->disabled_guids[$guid]);
173
+    }
174 174
 }
Please login to merge, or discard this patch.