Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

engine/classes/ElggEntity.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * The parent class for all Elgg Entities.
5
 *
6
 * An \ElggEntity is one of the basic data models in Elgg.  It is the primary
7
 * means of storing and retrieving data from the database.  An \ElggEntity
8
 * represents one row of the entities table.
9
 *
10
 * The \ElggEntity class handles CRUD operations for the entities table.
11
 * \ElggEntity should always be extended by another class to handle CRUD
12
 * operations on the type-specific table.
13
 *
14
 * \ElggEntity uses magic methods for get and set, so any property that isn't
15
 * declared will be assumed to be metadata and written to the database
16
 * as metadata on the object.  All children classes must declare which
17
 * properties are columns of the type table or they will be assumed
18
 * to be metadata.  See \ElggObject::initializeAttributes() for examples.
19
 *
20
 * Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and
21
 * \ElggSite.
22
 *
23
 * @tip Plugin authors will want to extend the \ElggObject class, not this class.
24
 *
25
 * @package    Elgg.Core
26
 * @subpackage DataModel.Entities
27
 *
28
 * @property       string $type           object, user, group, or site (read-only after save)
29
 * @property-write string $subtype        Further clarifies the nature of the entity (this should not be read)
30
 * @property-read  int    $guid           The unique identifier for this entity (read only)
31
 * @property       int    $owner_guid     The GUID of the owner of this entity (usually the creator)
32
 * @property       int    $container_guid The GUID of the entity containing this entity
33
 * @property       int    $access_id      Specifies the visibility level of this entity
34
 * @property       int    $time_created   A UNIX timestamp of when the entity was created
35
 * @property-read  int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save)
36
 * @property-read  int    $last_action    A UNIX timestamp of when the entity was last acted upon
37
 * @property       string $enabled        Is this entity enabled ('yes' or 'no')
38
 *
39
 * Metadata (the above are attributes)
40
 * @property       string $location       A location of the entity
41
 */
42
abstract class ElggEntity extends \ElggData implements
43
	Locatable, // Geocoding interface
44
	\Elgg\EntityIcon // Icon interface
45
{
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 = [];
80
81
	/**
82
	 * Create a new entity.
83
	 *
84
	 * Plugin developers should only use the constructor to create a new entity.
85
	 * To retrieve entities, use get_entity() and the elgg_get_entities* functions.
86
	 *
87
	 * If no arguments are passed, it creates a new entity.
88
	 * If a database result is passed as a \stdClass instance, it instantiates
89
	 * that entity.
90
	 *
91
	 * @param \stdClass $row Database row result. Default is null to create a new object.
92
	 *
93
	 * @throws IOException If cannot load remaining data from db
94
	 */
95 329
	public function __construct(\stdClass $row = null) {
96 329
		$this->initializeAttributes();
97
98 329
		if ($row && !$this->load($row)) {
99
			$msg = "Failed to load new " . get_class() . " for GUID:" . $row->guid;
100
			throw new \IOException($msg);
101
		}
102 329
	}
103
104
	/**
105
	 * Initialize the attributes array.
106
	 *
107
	 * This is vital to distinguish between metadata and base parameters.
108
	 *
109
	 * @return void
110
	 */
111 329
	protected function initializeAttributes() {
112 329
		parent::initializeAttributes();
113
114 329
		$this->attributes['guid'] = null;
115 329
		$this->attributes['type'] = null;
116 329
		$this->attributes['subtype'] = null;
117
118 329
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
119 329
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
120
121 329
		$this->attributes['access_id'] = ACCESS_PRIVATE;
122 329
		$this->attributes['time_updated'] = null;
123 329
		$this->attributes['last_action'] = null;
124 329
		$this->attributes['enabled'] = "yes";
125
126 329
		$this->attributes['type'] = $this->getType();
127 329
		$this->attributes += self::getExtraAttributeDefaults($this->getType());
128 329
	}
129
130
	/**
131
	 * Clone an entity
132
	 *
133
	 * Resets the guid so that the entity can be saved as a distinct entity from
134
	 * the original. Creation time will be set when this new entity is saved.
135
	 * The owner and container guids come from the original entity. The clone
136
	 * method copies metadata but does not copy annotations or private settings.
137
	 *
138
	 * @note metadata will have its owner and access id set when the entity is saved
139
	 * and it will be the same as that of the entity.
140
	 *
141
	 * @return void
142
	 */
143 1
	public function __clone() {
144 1
		$orig_entity = get_entity($this->guid);
145 1
		if (!$orig_entity) {
146
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
147
			return;
148
		}
149
150 1
		$metadata_array = elgg_get_metadata([
151 1
			'guid' => $this->guid,
152 1
			'limit' => 0
153
		]);
154
155 1
		$this->attributes['guid'] = null;
156 1
		$this->attributes['time_created'] = null;
157 1
		$this->attributes['time_updated'] = null;
158 1
		$this->attributes['last_action'] = null;
159
160 1
		$this->attributes['subtype'] = $orig_entity->getSubtype();
161
162
		// copy metadata over to new entity - slightly convoluted due to
163
		// handling of metadata arrays
164 1
		if (is_array($metadata_array)) {
165
			// create list of metadata names
166 1
			$metadata_names = [];
167 1
			foreach ($metadata_array as $metadata) {
168
				$metadata_names[] = $metadata['name'];
169
			}
170
			// arrays are stored with multiple enties per name
171 1
			$metadata_names = array_unique($metadata_names);
172
173
			// move the metadata over
174 1
			foreach ($metadata_names as $name) {
175
				$this->__set($name, $orig_entity->$name);
176
			}
177
		}
178 1
	}
179
180
	/**
181
	 * Set an attribute or metadata value for this entity
182
	 *
183
	 * Anything that is not an attribute is saved as metadata.
184
	 *
185
	 * @warning Metadata set this way will inherit the entity's owner and
186
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
187
	 *
188
	 * @param string $name  Name of the attribute or metadata
189
	 * @param mixed  $value The value to be set
190
	 * @return void
191
	 * @see \ElggEntity::setMetadata()
192
	 */
193 264
	public function __set($name, $value) {
194 264
		if ($this->$name === $value) {
195
			// quick return if value is not changing
196 203
			return;
197
		}
198
199 120
		if (array_key_exists($name, $this->attributes)) {
200
			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
201 115
			if (is_int($this->attributes[$name])
202 115
					&& is_string($value)
203 115
					&& ((string) $this->attributes[$name] === $value)) {
204
				return;
205
			}
206
207
			// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
208
			// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
209
			// the value to empty string, don't consider this a change.
210 115
			if (in_array($name, ['title', 'name', 'description'])
211 115
					&& $this->attributes[$name] === null
212 115
					&& $value === "") {
213
				return;
214
			}
215
216
			// keep original values
217 115
			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
218 5
				$this->orig_attributes[$name] = $this->attributes[$name];
219
			}
220
221
			// Certain properties should not be manually changed!
222
			switch ($name) {
223 115
				case 'guid':
224 115
				case 'time_updated':
225 115
				case 'last_action':
226 3
					return;
227
					break;
228 112
				case 'access_id':
229 109
				case 'owner_guid':
230 16 View Code Duplication
				case 'container_guid':
231 101
					if ($value !== null) {
232 101
						$this->attributes[$name] = (int) $value;
233
					} else {
234
						$this->attributes[$name] = null;
235
					}
236 101
					break;
237
				default:
238 15
					$this->attributes[$name] = $value;
239 15
					break;
240
			}
241 112
			return;
242
		}
243
244 106
		$this->setMetadata($name, $value);
245 106
	}
246
247
	/**
248
	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
249
	 *
250
	 * @return array
251
	 */
252 22
	public function getOriginalAttributes() {
253 22
		return $this->orig_attributes;
254
	}
255
256
	/**
257
	 * Get an attribute or metadata value
258
	 *
259
	 * If the name matches an attribute, the attribute is returned. If metadata
260
	 * does not exist with that name, a null is returned.
261
	 *
262
	 * This only returns an array if there are multiple values for a particular
263
	 * $name key.
264
	 *
265
	 * @param string $name Name of the attribute or metadata
266
	 * @return mixed
267
	 */
268 324
	public function __get($name) {
269 324
		if (array_key_exists($name, $this->attributes)) {
270 324
			if ($name === 'subtype' && $this->attributes['guid']) {
271 261
				_elgg_services()->logger->warn('Reading ->subtype on a persisted entity is unreliable.');
272
			}
273 324
			return $this->attributes[$name];
274
		}
275
276 131
		return $this->getMetadata($name);
277
	}
278
279
	/**
280
	 * Get the entity's display name
281
	 *
282
	 * @return string The title or name of this entity.
283
	 */
284 8
	public function getDisplayName() {
285 8
		$attr = $this->getSecondaryTableColumns()[0];
286 8
		return $this->$attr;
287
	}
288
289
	/**
290
	 * Sets the title or name of this entity.
291
	 *
292
	 * @param string $display_name The title or name of this entity.
293
	 * @return void
294
	 */
295 1
	public function setDisplayName($display_name) {
296 1
		$attr = $this->getSecondaryTableColumns()[0];
297 1
		$this->$attr = $display_name;
298 1
	}
299
300
	/**
301
	 * Return the value of a piece of metadata.
302
	 *
303
	 * @param string $name Name
304
	 *
305
	 * @return mixed The value, or null if not found.
306
	 */
307 131
	public function getMetadata($name) {
308 131
		$guid = $this->guid;
309
310 131
		if (!$guid) {
311 103
			if (isset($this->temp_metadata[$name])) {
312
				// md is returned as an array only if more than 1 entry
313 95
				if (count($this->temp_metadata[$name]) == 1) {
314 95
					return $this->temp_metadata[$name][0];
315
				} else {
316
					return $this->temp_metadata[$name];
317
				}
318
			} else {
319 103
				return null;
320
			}
321
		}
322
323
		// upon first cache miss, just load/cache all the metadata and retry.
324
		// if this works, the rest of this function may not be needed!
325 72
		$cache = _elgg_services()->metadataCache;
326 72
		if ($cache->isLoaded($guid)) {
327 53
			return $cache->getSingle($guid, $name);
328
		} else {
329 72
			$cache->populateFromEntities([$guid]);
330
			// in case ignore_access was on, we have to check again...
331 72
			if ($cache->isLoaded($guid)) {
332 72
				return $cache->getSingle($guid, $name);
333
			}
334
		}
335
336
		$md = elgg_get_metadata([
337
			'guid' => $guid,
338
			'metadata_name' => $name,
339
			'limit' => 0,
340
			'distinct' => false,
341
		]);
342
343
		$value = null;
344
345
		if ($md && !is_array($md)) {
346
			$value = $md->value;
347
		} elseif (count($md) == 1) {
348
			$value = $md[0]->value;
349
		} else if ($md && is_array($md)) {
350
			$value = metadata_array_to_values($md);
351
		}
352
353
		return $value;
354
	}
355
356
	/**
357
	 * Unset a property from metadata or attribute.
358
	 *
359
	 * @warning If you use this to unset an attribute, you must save the object!
360
	 *
361
	 * @param string $name The name of the attribute or metadata.
362
	 *
363
	 * @return void
364
	 * @todo some attributes should be set to null or other default values
365
	 */
366 58
	public function __unset($name) {
367 58
		if (array_key_exists($name, $this->attributes)) {
368 1
			$this->attributes[$name] = "";
369
		} else {
370 57
			$this->deleteMetadata($name);
371
		}
372 58
	}
373
374
	/**
375
	 * Set metadata on this entity.
376
	 *
377
	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
378
	 * Use this method if you want to explicitly set the owner or access of the metadata.
379
	 * You cannot set the owner/access before the entity has been saved.
380
	 *
381
	 * @param string $name       Name of the metadata
382
	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
383
	 * @param string $value_type 'text', 'integer', or '' for automatic detection
384
	 * @param bool   $multiple   Allow multiple values for a single name.
385
	 *                           Does not support associative arrays.
386
	 * @param int    $owner_guid GUID of entity that owns the metadata.
387
	 *                           Default is owner of entity.
388
	 *
389
	 * @return bool
390
	 * @throws InvalidArgumentException
391
	 */
392 106
	public function setMetadata($name, $value, $value_type = '', $multiple = false, $owner_guid = 0) {
393
394
		// normalize value to an array that we will loop over
395
		// remove indexes if value already an array.
396 106
		if (is_array($value)) {
397 1
			$value = array_values($value);
398
		} else {
399 106
			$value = [$value];
400
		}
401
402
		// saved entity. persist md to db.
403 106
		if ($this->guid) {
404
			// if overwriting, delete first.
405 51
			if (!$multiple) {
406
				$options = [
407 51
					'guid' => $this->getGUID(),
408 51
					'metadata_name' => $name,
409 51
					'limit' => 0
410
				];
411
				// @todo in 1.9 make this return false if can't add metadata
412
				// https://github.com/elgg/elgg/issues/4520
413
				//
414
				// need to remove access restrictions right now to delete
415
				// because this is the expected behavior
416 51
				$ia = elgg_set_ignore_access(true);
417 51
				if (false === elgg_delete_metadata($options)) {
418 51
					return false;
419
				}
420
				elgg_set_ignore_access($ia);
421
			}
422
423
			$owner_guid = $owner_guid ? (int) $owner_guid : $this->owner_guid;
424
425
			// add new md
426
			foreach ($value as $value_tmp) {
427
				// at this point $value is appended because it was cleared above if needed.
428
				$md_id = _elgg_services()->metadataTable->create($this->guid, $name, $value_tmp, $value_type,
429
						$owner_guid, null, true);
430
				if (!$md_id) {
431
					return false;
432
				}
433
			}
434
435
			return true;
436
		} else {
437
			// unsaved entity. store in temp array
438
439
			// returning single entries instead of an array of 1 element is decided in
440
			// getMetaData(), just like pulling from the db.
441
442 98
			if ($owner_guid != 0) {
443
				$msg = "owner guid cannot be used in ElggEntity::setMetadata() until entity is saved.";
444
				throw new \InvalidArgumentException($msg);
445
			}
446
447
			// if overwrite, delete first
448 98
			if (!$multiple || !isset($this->temp_metadata[$name])) {
449 98
				$this->temp_metadata[$name] = [];
450
			}
451
452
			// add new md
453 98
			$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
454 98
			return true;
455
		}
456
	}
457
458
	/**
459
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
460
	 * If you pass a name, only metadata matching that name will be deleted.
461
	 *
462
	 * @warning Calling this with no $name will clear all metadata on the entity.
463
	 *
464
	 * @param null|string $name The name of the metadata to remove.
465
	 * @return bool
466
	 * @since 1.8
467
	 */
468 58
	public function deleteMetadata($name = null) {
469
470 58
		if (!$this->guid) {
471 15
			return false;
472
		}
473
474
		$options = [
475 43
			'guid' => $this->guid,
476 43
			'limit' => 0
477
		];
478 43
		if ($name) {
479 42
			$options['metadata_name'] = $name;
480
		}
481
482 43
		return elgg_delete_metadata($options);
483
	}
484
485
	/**
486
	 * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
487
	 * If you pass a name, only metadata matching that name will be deleted.
488
	 *
489
	 * @param null|string $name The name of metadata to delete.
490
	 * @return bool
491
	 * @since 1.8
492
	 */
493 1 View Code Duplication
	public function deleteOwnedMetadata($name = null) {
494
		// access is turned off for this because they might
495
		// no longer have access to an entity they created metadata on.
496 1
		$ia = elgg_set_ignore_access(true);
497
		$options = [
498 1
			'metadata_owner_guid' => $this->guid,
499 1
			'limit' => 0
500
		];
501 1
		if ($name) {
502
			$options['metadata_name'] = $name;
503
		}
504
505 1
		$r = elgg_delete_metadata($options);
506 1
		elgg_set_ignore_access($ia);
507 1
		return $r;
508
	}
509
510
	/**
511
	 * Disables metadata for this entity, optionally based on name.
512
	 *
513
	 * @param string $name An options name of metadata to disable.
514
	 * @return bool
515
	 * @since 1.8
516
	 */
517 1 View Code Duplication
	public function disableMetadata($name = '') {
518
		$options = [
519 1
			'guid' => $this->guid,
520 1
			'limit' => 0
521
		];
522 1
		if ($name) {
523
			$options['metadata_name'] = $name;
524
		}
525
526 1
		return elgg_disable_metadata($options);
527
	}
528
529
	/**
530
	 * Enables metadata for this entity, optionally based on name.
531
	 *
532
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
533
	 *
534
	 * @param string $name An options name of metadata to enable.
535
	 * @return bool
536
	 * @since 1.8
537
	 */
538 View Code Duplication
	public function enableMetadata($name = '') {
539
		$options = [
540
			'guid' => $this->guid,
541
			'limit' => 0
542
		];
543
		if ($name) {
544
			$options['metadata_name'] = $name;
545
		}
546
547
		return elgg_enable_metadata($options);
548
	}
549
550
	/**
551
	 * Get a piece of volatile (non-persisted) data on this entity.
552
	 *
553
	 * @param string $name The name of the volatile data
554
	 *
555
	 * @return mixed The value or null if not found.
556
	 */
557 1
	public function getVolatileData($name) {
558 1
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
559
	}
560
561
	/**
562
	 * Set a piece of volatile (non-persisted) data on this entity
563
	 *
564
	 * @param string $name  Name
565
	 * @param mixed  $value Value
566
	 *
567
	 * @return void
568
	 */
569 8
	public function setVolatileData($name, $value) {
570 8
		$this->volatile[$name] = $value;
571 8
	}
572
573
	/**
574
	 * Cache the entity in a persisted cache
575
	 *
576
	 * @param ElggSharedMemoryCache $cache       Memcache or null cache
577
	 * @param int                   $last_action Last action time
578
	 *
579
	 * @return void
580
	 * @access private
581
	 * @internal
582
	 */
583 57
	public function storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action = 0) {
584 57
		$tmp = $this->volatile;
585
586
		// don't store volatile data
587 57
		$this->volatile = [];
588 57
		if ($last_action) {
589
			$this->attributes['last_action'] = (int) $last_action;
590
		}
591 57
		$cache->save($this->guid, $this);
592
593 57
		$this->volatile = $tmp;
594 57
	}
595
596
	/**
597
	 * Remove all relationships to and from this entity.
598
	 * If you pass a relationship name, only relationships matching that name
599
	 * will be deleted.
600
	 *
601
	 * @warning Calling this with no $relationship will clear all relationships
602
	 * for this entity.
603
	 *
604
	 * @param null|string $relationship The name of the relationship to remove.
605
	 * @return bool
606
	 * @see \ElggEntity::addRelationship()
607
	 * @see \ElggEntity::removeRelationship()
608
	 */
609 1
	public function deleteRelationships($relationship = null) {
610 1
		$relationship = (string) $relationship;
611 1
		$result = remove_entity_relationships($this->getGUID(), $relationship);
612 1
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
613
	}
614
615
	/**
616
	 * Add a relationship between this an another entity.
617
	 *
618
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
619
	 *
620
	 * @param int    $guid_two     GUID of the target entity of the relationship.
621
	 * @param string $relationship The type of relationship.
622
	 *
623
	 * @return bool
624
	 * @see \ElggEntity::removeRelationship()
625
	 * @see \ElggEntity::deleteRelationships()
626
	 */
627 1
	public function addRelationship($guid_two, $relationship) {
628 1
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
629
	}
630
631
	/**
632
	 * Remove a relationship
633
	 *
634
	 * @param int    $guid_two     GUID of the target entity of the relationship.
635
	 * @param string $relationship The type of relationship.
636
	 *
637
	 * @return bool
638
	 * @see \ElggEntity::addRelationship()
639
	 * @see \ElggEntity::deleteRelationships()
640
	 */
641 1
	public function removeRelationship($guid_two, $relationship) {
642 1
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
643
	}
644
645
	/**
646
	 * Adds a private setting to this entity.
647
	 *
648
	 * Private settings are similar to metadata but will not
649
	 * be searched and there are fewer helper functions for them.
650
	 *
651
	 * @param string $name  Name of private setting
652
	 * @param mixed  $value Value of private setting
653
	 *
654
	 * @return bool
655
	 */
656 11
	public function setPrivateSetting($name, $value) {
657 11
		if ((int) $this->guid > 0) {
658 5
			return set_private_setting($this->getGUID(), $name, $value);
659
		} else {
660 11
			$this->temp_private_settings[$name] = $value;
661 11
			return true;
662
		}
663
	}
664
665
	/**
666
	 * Returns a private setting value
667
	 *
668
	 * @param string $name Name of the private setting
669
	 *
670
	 * @return mixed Null if the setting does not exist
671
	 */
672 11
	public function getPrivateSetting($name) {
673 11
		if ((int) ($this->guid) > 0) {
674 5
			return get_private_setting($this->getGUID(), $name);
675
		} else {
676 11
			if (isset($this->temp_private_settings[$name])) {
677 10
				return $this->temp_private_settings[$name];
678
			}
679
		}
680 2
		return null;
681
	}
682
683
	/**
684
	 * Removes private setting
685
	 *
686
	 * @param string $name Name of the private setting
687
	 *
688
	 * @return bool
689
	 */
690
	public function removePrivateSetting($name) {
691
		return remove_private_setting($this->getGUID(), $name);
692
	}
693
694
	/**
695
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
696
	 * If you pass a name, only annotations matching that name will be deleted.
697
	 *
698
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
699
	 *
700
	 * @param null|string $name The annotations name to remove.
701
	 * @return bool
702
	 * @since 1.8
703
	 */
704 1 View Code Duplication
	public function deleteAnnotations($name = null) {
705
		$options = [
706 1
			'guid' => $this->guid,
707 1
			'limit' => 0
708
		];
709 1
		if ($name) {
710
			$options['annotation_name'] = $name;
711
		}
712
713 1
		return elgg_delete_annotations($options);
714
	}
715
716
	/**
717
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
718
	 * If you pass a name, only annotations matching that name will be deleted.
719
	 *
720
	 * @param null|string $name The name of annotations to delete.
721
	 * @return bool
722
	 * @since 1.8
723
	 */
724 1 View Code Duplication
	public function deleteOwnedAnnotations($name = null) {
725
		// access is turned off for this because they might
726
		// no longer have access to an entity they created annotations on.
727 1
		$ia = elgg_set_ignore_access(true);
728
		$options = [
729 1
			'annotation_owner_guid' => $this->guid,
730 1
			'limit' => 0
731
		];
732 1
		if ($name) {
733
			$options['annotation_name'] = $name;
734
		}
735
736 1
		$r = elgg_delete_annotations($options);
737 1
		elgg_set_ignore_access($ia);
738 1
		return $r;
739
	}
740
741
	/**
742
	 * Disables annotations for this entity, optionally based on name.
743
	 *
744
	 * @param string $name An options name of annotations to disable.
745
	 * @return bool
746
	 * @since 1.8
747
	 */
748 1 View Code Duplication
	public function disableAnnotations($name = '') {
749
		$options = [
750 1
			'guid' => $this->guid,
751 1
			'limit' => 0
752
		];
753 1
		if ($name) {
754
			$options['annotation_name'] = $name;
755
		}
756
757 1
		return elgg_disable_annotations($options);
758
	}
759
760
	/**
761
	 * Enables annotations for this entity, optionally based on name.
762
	 *
763
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
764
	 *
765
	 * @param string $name An options name of annotations to enable.
766
	 * @return bool
767
	 * @since 1.8
768
	 */
769 View Code Duplication
	public function enableAnnotations($name = '') {
770
		$options = [
771
			'guid' => $this->guid,
772
			'limit' => 0
773
		];
774
		if ($name) {
775
			$options['annotation_name'] = $name;
776
		}
777
778
		return elgg_enable_annotations($options);
779
	}
780
781
	/**
782
	 * Helper function to return annotation calculation results
783
	 *
784
	 * @param string $name        The annotation name.
785
	 * @param string $calculation A valid MySQL function to run its values through
786
	 * @return mixed
787
	 */
788
	private function getAnnotationCalculation($name, $calculation) {
789
		$options = [
790
			'guid' => $this->getGUID(),
791
			'distinct' => false,
792
			'annotation_name' => $name,
793
			'annotation_calculation' => $calculation
794
		];
795
796
		return elgg_get_annotations($options);
797
	}
798
799
	/**
800
	 * Adds an annotation to an entity.
801
	 *
802
	 * @warning By default, annotations are private.
803
	 *
804
	 * @warning Annotating an unsaved entity more than once with the same name
805
	 *          will only save the last annotation.
806
	 *
807
	 * @param string $name       Annotation name
808
	 * @param mixed  $value      Annotation value
809
	 * @param int    $access_id  Access ID
810
	 * @param int    $owner_guid GUID of the annotation owner
811
	 * @param string $vartype    The type of annotation value
812
	 *
813
	 * @return bool|int Returns int if an annotation is saved
814
	 */
815 84
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
816 84
		if ((int) $this->guid > 0) {
817 84
			return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
818
		} else {
819
			$this->temp_annotations[$name] = $value;
820
		}
821
		return true;
822
	}
823
824
	/**
825
	 * Gets an array of annotations.
826
	 *
827
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
828
	 * as the options array.
829
	 *
830
	 * @param array $options Array of options for elgg_get_annotations() except guid.
831
	 *
832
	 * @return array
833
	 * @see elgg_get_annotations()
834
	 */
835
	public function getAnnotations(array $options = []) {
836
		if ($this->guid) {
837
			$options['guid'] = $this->guid;
838
839
			return elgg_get_annotations($options);
840
		} else {
841
			$name = elgg_extract('annotation_name', $options, '');
842
843
			if (isset($this->temp_annotations[$name])) {
844
				return [$this->temp_annotations[$name]];
845
			}
846
		}
847
848
		return [];
849
	}
850
851
	/**
852
	 * Count annotations.
853
	 *
854
	 * @param string $name The type of annotation.
855
	 *
856
	 * @return int
857
	 */
858
	public function countAnnotations($name = "") {
859
		return $this->getAnnotationCalculation($name, 'count');
860
	}
861
862
	/**
863
	 * Get the average of an integer type annotation.
864
	 *
865
	 * @param string $name Annotation name
866
	 *
867
	 * @return int
868
	 */
869
	public function getAnnotationsAvg($name) {
870
		return $this->getAnnotationCalculation($name, 'avg');
871
	}
872
873
	/**
874
	 * Get the sum of integer type annotations of a given name.
875
	 *
876
	 * @param string $name Annotation name
877
	 *
878
	 * @return int
879
	 */
880
	public function getAnnotationsSum($name) {
881
		return $this->getAnnotationCalculation($name, 'sum');
882
	}
883
884
	/**
885
	 * Get the minimum of integer type annotations of given name.
886
	 *
887
	 * @param string $name Annotation name
888
	 *
889
	 * @return int
890
	 */
891
	public function getAnnotationsMin($name) {
892
		return $this->getAnnotationCalculation($name, 'min');
893
	}
894
895
	/**
896
	 * Get the maximum of integer type annotations of a given name.
897
	 *
898
	 * @param string $name Annotation name
899
	 *
900
	 * @return int
901
	 */
902
	public function getAnnotationsMax($name) {
903
		return $this->getAnnotationCalculation($name, 'max');
904
	}
905
906
	/**
907
	 * Count the number of comments attached to this entity.
908
	 *
909
	 * @return int Number of comments
910
	 * @since 1.8.0
911
	 */
912
	public function countComments() {
913
		$params = ['entity' => $this];
914
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
915
916
		if (is_int($num)) {
917
			return $num;
918
		} else {
919
			return elgg_get_entities([
920
				'type' => 'object',
921
				'subtype' => 'comment',
922
				'container_guid' => $this->getGUID(),
923
				'count' => true,
924
				'distinct' => false,
925
			]);
926
		}
927
	}
928
929
	/**
930
	 * Gets an array of entities with a relationship to this entity.
931
	 *
932
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
933
	 *                       for a list of options. 'relationship_guid' is set to
934
	 *                       this entity.
935
	 *
936
	 * @return array|false An array of entities or false on failure
937
	 * @see elgg_get_entities_from_relationship()
938
	 */
939
	public function getEntitiesFromRelationship(array $options = []) {
940
		$options['relationship_guid'] = $this->guid;
941
		return elgg_get_entities_from_relationship($options);
942
	}
943
944
	/**
945
	 * Gets the number of entities from a specific relationship type
946
	 *
947
	 * @param string $relationship         Relationship type (eg "friends")
948
	 * @param bool   $inverse_relationship Invert relationship
949
	 *
950
	 * @return int|false The number of entities or false on failure
951
	 */
952
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
953
		return elgg_get_entities_from_relationship([
954
			'relationship' => $relationship,
955
			'relationship_guid' => $this->getGUID(),
956
			'inverse_relationship' => $inverse_relationship,
957
			'count' => true
958
		]);
959
	}
960
961
	/**
962
	 * Can a user edit this entity?
963
	 *
964
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
965
	 *
966
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
967
	 *
968
	 * @return bool Whether this entity is editable by the given user.
969
	 * @see elgg_set_ignore_access()
970
	 */
971 22
	public function canEdit($user_guid = 0) {
972 22
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
973
	}
974
975
	/**
976
	 * Can a user delete this entity?
977
	 *
978
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
979
	 *
980
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
981
	 *
982
	 * @return bool Whether this entity is deletable by the given user.
983
	 * @since 1.11
984
	 * @see elgg_set_ignore_access()
985
	 */
986 4
	public function canDelete($user_guid = 0) {
987 4
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
988
	}
989
990
	/**
991
	 * Can a user edit metadata on this entity?
992
	 *
993
	 * If no specific metadata is passed, it returns whether the user can
994
	 * edit any metadata on the entity.
995
	 *
996
	 * @tip Can be overridden by by registering for the permissions_check:metadata
997
	 * plugin hook.
998
	 *
999
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1000
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1001
	 *
1002
	 * @return bool
1003
	 * @see elgg_set_ignore_access()
1004
	 */
1005 8
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1006 8
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1007
	}
1008
1009
	/**
1010
	 * Can a user add an entity to this container
1011
	 *
1012
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1013
	 * @param string $type      The type of entity we're looking to write
1014
	 * @param string $subtype   The subtype of the entity we're looking to write
1015
	 *
1016
	 * @return bool
1017
	 * @see elgg_set_ignore_access()
1018
	 */
1019 5
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1020 5
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1021
	}
1022
1023
	/**
1024
	 * Can a user comment on an entity?
1025
	 *
1026
	 * @tip Can be overridden by registering for the permissions_check:comment,
1027
	 * <entity type> plugin hook.
1028
	 *
1029
	 * @param int  $user_guid User guid (default is logged in user)
1030
	 * @param bool $default   Default permission
1031
	 * @return bool
1032
	 */
1033 5
	public function canComment($user_guid = 0, $default = null) {
1034 5
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1035
	}
1036
1037
	/**
1038
	 * Can a user annotate an entity?
1039
	 *
1040
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1041
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1042
	 *
1043
	 * @tip If you want logged out users to annotate an object, do not call
1044
	 * canAnnotate(). It's easier than using the plugin hook.
1045
	 *
1046
	 * @param int    $user_guid       User guid (default is logged in user)
1047
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1048
	 *
1049
	 * @return bool
1050
	 */
1051 8
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1052 8
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1053
	}
1054
1055
	/**
1056
	 * Returns the access_id.
1057
	 *
1058
	 * @return int The access ID
1059
	 */
1060 1
	public function getAccessID() {
1061 1
		return $this->access_id;
1062
	}
1063
1064
	/**
1065
	 * Returns the guid.
1066
	 *
1067
	 * @return int|null GUID
1068
	 */
1069 155
	public function getGUID() {
1070 155
		return $this->guid;
1071
	}
1072
1073
	/**
1074
	 * Returns the entity type
1075
	 *
1076
	 * @return string The entity type
1077
	 */
1078 1
	public function getType() {
1079
		// this is just for the PHPUnit mocking framework
1080 1
		return $this->type;
1081
	}
1082
1083
	/**
1084
	 * Get the entity subtype
1085
	 *
1086
	 * @return string The entity subtype
1087
	 */
1088 88
	public function getSubtype() {
1089
		// If this object hasn't been saved, then return the subtype string.
1090 88
		if ($this->attributes['guid']) {
1091 83
			return get_subtype_from_id($this->attributes['subtype']);
1092
		}
1093 8
		return $this->attributes['subtype'];
1094
	}
1095
1096
	/**
1097
	 * Get the guid of the entity's owner.
1098
	 *
1099
	 * @return int The owner GUID
1100
	 */
1101 79
	public function getOwnerGUID() {
1102 79
		return (int) $this->owner_guid;
1103
	}
1104
1105
	/**
1106
	 * Gets the \ElggEntity that owns this entity.
1107
	 *
1108
	 * @return \ElggEntity The owning entity
1109
	 */
1110 1
	public function getOwnerEntity() {
1111 1
		return get_entity($this->owner_guid);
1112
	}
1113
1114
	/**
1115
	 * Set the container for this object.
1116
	 *
1117
	 * @param int $container_guid The ID of the container.
1118
	 *
1119
	 * @return bool
1120
	 */
1121
	public function setContainerGUID($container_guid) {
1122
		return $this->container_guid = (int) $container_guid;
1123
	}
1124
1125
	/**
1126
	 * Gets the container GUID for this entity.
1127
	 *
1128
	 * @return int
1129
	 */
1130 12
	public function getContainerGUID() {
1131 12
		return (int) $this->container_guid;
1132
	}
1133
1134
	/**
1135
	 * Get the container entity for this object.
1136
	 *
1137
	 * @return \ElggEntity
1138
	 * @since 1.8.0
1139
	 */
1140 8
	public function getContainerEntity() {
1141 8
		return get_entity($this->getContainerGUID());
1142
	}
1143
1144
	/**
1145
	 * Returns the UNIX epoch time that this entity was last updated
1146
	 *
1147
	 * @return int UNIX epoch time
1148
	 */
1149 3
	public function getTimeUpdated() {
1150 3
		return $this->time_updated;
1151
	}
1152
1153
	/**
1154
	 * Gets the URL for this entity.
1155
	 *
1156
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1157
	 * customize the url for an entity.
1158
	 *
1159
	 * @return string The URL of the entity
1160
	 */
1161 11
	public function getURL() {
1162 11
		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
1163
		
1164 11
		if ($url === null || $url === '' || $url === false) {
1165 11
			return '';
1166
		}
1167
1168
		return elgg_normalize_url($url);
1169
	}
1170
1171
	/**
1172
	 * Saves icons using an uploaded file as the source.
1173
	 *
1174
	 * @param string $input_name Form input name
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 saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1180
		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1181
	}
1182
1183
	/**
1184
	 * Saves icons using a local file as the source.
1185
	 *
1186
	 * @param string $filename The full path to the local file
1187
	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1188
	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1189
	 * @return bool
1190
	 */
1191
	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1192
		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1193
	}
1194
1195
	/**
1196
	 * Saves icons using a file located in the data store as the source.
1197
	 *
1198
	 * @param string $file   An ElggFile instance
1199
	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1200
	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1201
	 * @return bool
1202
	 */
1203
	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1204
		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1205
	}
1206
	
1207
	/**
1208
	 * Returns entity icon as an ElggIcon object
1209
	 * The icon file may or may not exist on filestore
1210
	 *
1211
	 * @param string $size Size of the icon
1212
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1213
	 * @return \ElggIcon
1214
	 */
1215 1
	public function getIcon($size, $type = 'icon') {
1216 1
		return _elgg_services()->iconService->getIcon($this, $size, $type);
1217
	}
1218
1219
	/**
1220
	 * Removes all icon files and metadata for the passed type of icon.
1221
	 *
1222
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1223
	 * @return bool
1224
	 */
1225
	public function deleteIcon($type = 'icon') {
1226
		return _elgg_services()->iconService->deleteIcon($this, $type);
1227
	}
1228
	
1229
	/**
1230
	 * Returns the timestamp of when the icon was changed.
1231
	 *
1232
	 * @param string $size The size of the icon
1233
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1234
	 *
1235
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1236
	 */
1237
	public function getIconLastChange($size, $type = 'icon') {
1238
		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1239
	}
1240
	
1241
	/**
1242
	 * Returns if the entity has an icon of the passed type.
1243
	 *
1244
	 * @param string $size The size of the icon
1245
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1246
	 * @return bool
1247
	 */
1248
	public function hasIcon($size, $type = 'icon') {
1249
		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1250
	}
1251
1252
	/**
1253
	 * Get the URL for this entity's icon
1254
	 *
1255
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1256
	 * to customize the icon for an entity.
1257
	 *
1258
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1259
	 *                      or an array of parameters including 'size'
1260
	 * @return string The URL
1261
	 * @since 1.8.0
1262
	 */
1263
	public function getIconURL($params = []) {
1264
		return _elgg_services()->iconService->getIconURL($this, $params);
1265
	}
1266
1267
	/**
1268
	 * Save an entity.
1269
	 *
1270
	 * @return bool|int
1271
	 * @throws InvalidParameterException
1272
	 * @throws IOException
1273
	 */
1274 8
	public function save() {
1275 8
		$guid = $this->guid;
1276 8
		if ($guid > 0) {
1277 2
			$guid = $this->update();
1278
		} else {
1279 6
			$guid = $this->create();
1280 6
			if ($guid && !_elgg_services()->events->trigger('create', $this->type, $this)) {
1281
				// plugins that return false to event don't need to override the access system
1282
				$ia = elgg_set_ignore_access(true);
1283
				$this->delete();
1284
				elgg_set_ignore_access($ia);
1285
				return false;
1286
			}
1287
		}
1288
1289 8
		if ($guid) {
1290 7
			_elgg_services()->entityCache->set($this);
1291 7
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1292
		}
1293
1294 8
		return $guid;
1295
	}
1296
1297
	/**
1298
	 * Create a new entry in the entities table.
1299
	 *
1300
	 * Saves the base information in the entities table for the entity.  Saving
1301
	 * the type-specific information is handled in the calling class method.
1302
	 *
1303
	 * @warning Entities must have an entry in both the entities table and their type table
1304
	 * or they will throw an exception when loaded.
1305
	 *
1306
	 * @return int The new entity's GUID
1307
	 * @throws InvalidParameterException If the entity's type has not been set.
1308
	 * @throws IOException If the new row fails to write to the DB.
1309
	 */
1310 6
	protected function create() {
1311
1312 6
		$allowed_types = elgg_get_config('entity_types');
1313 6
		$type = $this->getDatabase()->sanitizeString($this->attributes['type']);
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\Database::sanitizeString() has been deprecated with message: Use query parameters where possible

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1314 6
		if (!in_array($type, $allowed_types)) {
1315
			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1316
					. implode(', ', $allowed_types));
1317
		}
1318
		
1319 6
		$subtype = $this->attributes['subtype'];
1320 6
		$subtype_id = add_subtype($type, $subtype);
1321 6
		$owner_guid = (int) $this->attributes['owner_guid'];
1322 6
		$access_id = (int) $this->attributes['access_id'];
1323 6
		$now = $this->getCurrentTime()->getTimestamp();
1324 6
		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1325
		
1326 6
		$container_guid = $this->attributes['container_guid'];
1327 6
		if ($container_guid == 0) {
1328 5
			$container_guid = $owner_guid;
1329 5
			$this->attributes['container_guid'] = $container_guid;
1330
		}
1331 6
		$container_guid = (int) $container_guid;
1332
1333 6
		if ($access_id == ACCESS_DEFAULT) {
1334
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1335
		}
1336
1337 6
		$user_guid = elgg_get_logged_in_user_guid();
1338
1339
		// If given an owner, verify it can be loaded
1340 6 View Code Duplication
		if ($owner_guid) {
1341 1
			$owner = $this->getOwnerEntity();
1342 1
			if (!$owner) {
1343
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1344
					. " owner $owner_guid could not be loaded.");
1345
				return false;
1346
			}
1347
1348
			// If different owner than logged in, verify can write to container.
1349
1350 1
			if ($user_guid != $owner_guid && !$owner->canWriteToContainer(0, $type, $subtype)) {
1351
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1352
					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1353
				return false;
1354
			}
1355
		}
1356
1357
		// If given a container, verify it can be loaded and that the current user can write to it
1358 6 View Code Duplication
		if ($container_guid) {
1359 1
			$container = $this->getContainerEntity();
1360 1
			if (!$container) {
1361
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1362
					. " container $container_guid could not be loaded.");
1363
				return false;
1364
			}
1365
1366 1
			if (!$container->canWriteToContainer(0, $type, $subtype)) {
1367
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1368
					. " permitted to write to container $container_guid.");
1369
				return false;
1370
			}
1371
		}
1372
1373
		// Create primary table row
1374 6
		$guid = _elgg_services()->entityTable->insertRow((object) [
1375 6
			'type' => $type,
1376 6
			'subtype_id' => $subtype_id,
1377 6
			'owner_guid' => $owner_guid,
1378 6
			'container_guid' => $container_guid,
1379 6
			'access_id' => $access_id,
1380 6
			'time_created' => $time_created,
1381 6
			'time_updated' => $now,
1382 6
			'last_action' => $now,
1383 6
		], $this->attributes);
1384
1385 6
		if (!$guid) {
1386
			throw new \IOException("Unable to save new object's base entity information!");
1387
		}
1388
1389
		// We are writing this new entity to cache to make sure subsequent calls
1390
		// to get_entity() load the entity from cache and not from the DB. This
1391
		// MUST come before the metadata and annotation writes below!
1392 6
		_elgg_services()->entityCache->set($this);
1393
	
1394
		// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1395 6
		$this->attributes['subtype'] = (int) $subtype_id;
1396 6
		$this->attributes['guid'] = (int) $guid;
1397 6
		$this->attributes['time_created'] = (int) $time_created;
1398 6
		$this->attributes['time_updated'] = (int) $now;
1399 6
		$this->attributes['last_action'] = (int) $now;
1400 6
		$this->attributes['container_guid'] = (int) $container_guid;
1401
1402
		// Create secondary table row
1403 6
		$attrs = $this->getSecondaryTableColumns();
1404
1405 6
		$column_names = implode(', ', $attrs);
1406
		$values = implode(', ', array_map(function ($attr) {
1407 6
			return ":$attr";
1408 6
		}, $attrs));
1409
1410
		$params = [
1411 6
			':guid' => $guid,
1412
		];
1413 6 View Code Duplication
		foreach ($attrs as $attr) {
1414 6
			$params[":$attr"] = ($attr === 'url') ? '' : (string) $this->attributes[$attr];
1415
		}
1416
1417 6
		$db = $this->getDatabase();
1418
		$query = "
1419 6
			INSERT INTO {$db->prefix}{$this->type}s_entity
1420 6
			(guid, $column_names) VALUES (:guid, $values)
1421
		";
1422
1423 6
		if ($db->insertData($query, $params) === false) {
1424
			// Uh oh, couldn't save secondary
1425
			$query = "
1426
				DELETE FROM {$db->prefix}entities
1427
				WHERE guid = :guid
1428
			";
1429
			$params = [
1430
				':guid' => $guid,
1431
			];
1432
			$db->deleteData($query, $params);
1433
1434
			_elgg_services()->entityCache->remove($guid);
1435
1436
			throw new \IOException("Unable to save new object's secondary entity information!");
1437
		}
1438
1439
		// Save any unsaved metadata
1440 6
		if (sizeof($this->temp_metadata) > 0) {
1441
			foreach ($this->temp_metadata as $name => $value) {
1442
				$this->$name = $value;
1443
			}
1444
1445
			$this->temp_metadata = [];
1446
		}
1447
1448
		// Save any unsaved annotations.
1449 6
		if (sizeof($this->temp_annotations) > 0) {
1450
			foreach ($this->temp_annotations as $name => $value) {
1451
				$this->annotate($name, $value);
1452
			}
1453
1454
			$this->temp_annotations = [];
1455
		}
1456
1457
		// Save any unsaved private settings.
1458 6
		if (sizeof($this->temp_private_settings) > 0) {
1459 5
			foreach ($this->temp_private_settings as $name => $value) {
1460 5
				$this->setPrivateSetting($name, $value);
1461
			}
1462
1463 5
			$this->temp_private_settings = [];
1464
		}
1465
		
1466 6
		return $guid;
1467
	}
1468
1469
	/**
1470
	 * Update the entity in the database.
1471
	 *
1472
	 * @return bool Whether the update was successful.
1473
	 *
1474
	 * @throws InvalidParameterException
1475
	 */
1476 2
	protected function update() {
1477
		
1478 2
		_elgg_services()->boot->invalidateCache($this->guid);
1479
1480 2
		if (!$this->canEdit()) {
1481 1
			return false;
1482
		}
1483
1484
		// give old update event a chance to stop the update
1485 1
		if (!_elgg_services()->events->trigger('update', $this->type, $this)) {
1486
			return false;
1487
		}
1488
1489
		// See #6225. We copy these after the update event in case a handler changed one of them.
1490 1
		$guid = (int) $this->guid;
1491 1
		$owner_guid = (int) $this->owner_guid;
1492 1
		$access_id = (int) $this->access_id;
1493 1
		$container_guid = (int) $this->container_guid;
1494 1
		$time_created = (int) $this->time_created;
1495 1
		$time = $this->getCurrentTime()->getTimestamp();
1496
1497 1
		if ($access_id == ACCESS_DEFAULT) {
1498
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1499
		}
1500
1501
		// Update primary table
1502 1
		$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1503 1
			'owner_guid' => $owner_guid,
1504 1
			'container_guid' => $container_guid,
1505 1
			'access_id' => $access_id,
1506 1
			'time_created' => $time_created,
1507 1
			'time_updated' => $time,
1508 1
			'guid' => $guid,
1509
		]);
1510 1
		if ($ret === false) {
1511
			return false;
1512
		}
1513
1514 1
		$this->attributes['time_updated'] = $time;
1515
1516
		// Update secondary table
1517 1
		$attrs = $this->getSecondaryTableColumns();
1518
1519 1
		$sets = array_map(function ($attr) {
1520 1
			return "$attr = :$attr";
1521 1
		}, $attrs);
1522 1
		$sets = implode(', ', $sets);
1523
1524 1 View Code Duplication
		foreach ($attrs as $attr) {
1525 1
			$params[":$attr"] = ($attr === 'url') ? '' : (string) $this->attributes[$attr];
1526
		}
1527 1
		$params[':guid'] = $this->guid;
1528
1529 1
		$db = $this->getDatabase();
1530
		$query = "
1531 1
			UPDATE {$db->prefix}{$this->type}s_entity
1532 1
			SET $sets
1533
			WHERE guid = :guid
1534
		";
1535
1536 1
		if ($db->updateData($query, false, $params) === false) {
1537
			return false;
1538
		}
1539
1540 1
		elgg_trigger_after_event('update', $this->type, $this);
1541
1542
		// TODO(evan): Move this to \ElggObject?
1543 1
		if ($this instanceof \ElggObject) {
1544 1
			update_river_access_by_object($guid, $access_id);
1545
		}
1546
1547 1
		$this->orig_attributes = [];
1548
1549
		// Handle cases where there was no error BUT no rows were updated!
1550 1
		return true;
1551
	}
1552
1553
	/**
1554
	 * Loads attributes from the entities table into the object.
1555
	 *
1556
	 * @param \stdClass $row Object of properties from database row(s)
1557
	 *
1558
	 * @return bool
1559
	 */
1560 261
	protected function load(\stdClass $row) {
1561 261
		$type = $this->type;
1562
1563 261
		$attr_loader = new \Elgg\AttributeLoader(get_class($this), $type, $this->attributes);
1564 261
		if ($type === 'user' || $this instanceof ElggPlugin) {
1565 220
			$attr_loader->requires_access_control = false;
1566
		}
1567 261
		$attr_loader->secondary_loader = "get_{$type}_entity_as_row";
1568
1569 261
		$attrs = $attr_loader->getRequiredAttributes($row);
1570 261
		if (!$attrs) {
1571
			return false;
1572
		}
1573
1574 261
		$this->attributes = $attrs;
1575
1576 261
		foreach ($attr_loader->getAdditionalSelectValues() as $name => $value) {
1577 7
			$this->setVolatileData("select:$name", $value);
1578
		}
1579
1580 261
		_elgg_services()->entityCache->set($this);
1581
1582 261
		return true;
1583
	}
1584
1585
	/**
1586
	 * Get the added columns (besides GUID) stored in the secondary table
1587
	 *
1588
	 * @return string[]
1589
	 * @throws \InvalidArgumentException
1590
	 */
1591 14
	private function getSecondaryTableColumns() {
1592
		// Note: the title or name column must come first. See getDisplayName().
1593 14
		if ($this instanceof ElggObject) {
1594 11
			return ['title', 'description'];
1595
		}
1596 4
		if ($this instanceof ElggUser) {
1597 4
			return ['name', 'username', 'password_hash', 'email', 'language'];
1598
		}
1599 2
		if ($this instanceof ElggGroup) {
1600 1
			return ['name', 'description'];
1601
		}
1602 1
		if ($this instanceof ElggSite) {
1603 1
			return ['name', 'description', 'url'];
1604
		}
1605
		throw new \InvalidArgumentException("Not a recognized type: " . get_class($this));
1606
	}
1607
1608
	/**
1609
	 * Get default values for the attributes not defined in \ElggEntity::initializeAttributes
1610
	 *
1611
	 * @param string $type Entity type
1612
	 *
1613
	 * @return array
1614
	 * @access private
1615
	 */
1616 329
	public static function getExtraAttributeDefaults($type) {
1617
		switch ($type) {
1618 329
			case 'object':
1619
				return [
1620 273
					'title' => null,
1621
					'description' => null,
1622
				];
1623 269
			case 'user':
1624
				return [
1625 223
					'name' => null,
1626
					'username' => null,
1627
					'password_hash' => null,
1628
					'email' => null,
1629
					'language' => null,
1630
					'banned' => "no",
1631
					'admin' => 'no',
1632
					'prev_last_action' => null,
1633
					'last_login' => null,
1634
					'prev_last_login' => null,
1635
				];
1636 261
			case 'group':
1637
				return [
1638 87
					'name' => null,
1639
					'description' => null,
1640
				];
1641 260
			case 'site':
1642
				return [
1643 260
					'name' => null,
1644
					'description' => null,
1645
					'url' => null,
1646
				];
1647
		}
1648
		throw new \InvalidArgumentException("Not a recognized type: $type");
1649
	}
1650
	
1651
	/**
1652
	 * Load new data from database into existing entity. Overwrites data but
1653
	 * does not change values not included in the latest data.
1654
	 *
1655
	 * @internal This is used when the same entity is selected twice during a
1656
	 * request in case different select clauses were used to load different data
1657
	 * into volatile data.
1658
	 *
1659
	 * @param \stdClass $row DB row with new entity data
1660
	 * @return bool
1661
	 * @access private
1662
	 */
1663
	public function refresh(\stdClass $row) {
1664
		if ($row instanceof \stdClass) {
1665
			return $this->load($row);
1666
		}
1667
		return false;
1668
	}
1669
1670
	/**
1671
	 * Disable this entity.
1672
	 *
1673
	 * Disabled entities are not returned by getter functions.
1674
	 * To enable an entity, use {@link \ElggEntity::enable()}.
1675
	 *
1676
	 * Recursively disabling an entity will disable all entities
1677
	 * owned or contained by the parent entity.
1678
	 *
1679
	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1680
	 *
1681
	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1682
	 *
1683
	 * @param string $reason    Optional reason
1684
	 * @param bool   $recursive Recursively disable all contained entities?
1685
	 *
1686
	 * @return bool
1687
	 * @see \ElggEntity::enable()
1688
	 */
1689 2
	public function disable($reason = "", $recursive = true) {
1690 2
		if (!$this->guid) {
1691 1
			return false;
1692
		}
1693
		
1694 1
		if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
1695
			return false;
1696
		}
1697
		
1698 1
		if (!$this->canEdit()) {
1699
			return false;
1700
		}
1701
1702 1
		if ($this instanceof ElggUser && $this->banned === 'no') {
1703
			// temporarily ban to prevent using the site during disable
1704
			_elgg_services()->usersTable->markBanned($this->guid, true);
1705
			$unban_after = true;
1706
		} else {
1707 1
			$unban_after = false;
1708
		}
1709
1710 1
		if ($reason) {
1711
			$this->disable_reason = $reason;
1712
		}
1713
1714 1
		$dbprefix = _elgg_config()->dbprefix;
1715
		
1716 1
		$guid = (int) $this->guid;
1717
		
1718 1
		if ($recursive) {
1719
			// Only disable enabled subentities
1720 1
			$hidden = access_get_show_hidden_status();
1721 1
			access_show_hidden_entities(false);
1722
1723 1
			$ia = elgg_set_ignore_access(true);
1724
1725
			$base_options = [
1726 1
				'wheres' => [
1727 1
					"e.guid != $guid",
1728
				],
1729
				'limit' => false,
1730
			];
1731
			
1732 1
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1733 1
				$options = $base_options;
1734 1
				$options[$db_column] = $guid;
1735
				
1736 1
				$subentities = new \ElggBatch('elgg_get_entities', $options);
1737 1
				$subentities->setIncrementOffset(false);
1738
				
1739 1
				foreach ($subentities as $subentity) {
1740
					/* @var $subentity \ElggEntity */
1741
					if (!$subentity->isEnabled()) {
1742
						continue;
1743
					}
1744
					add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1745 1
					$subentity->disable($reason);
1746
				}
1747
			}
1748
1749 1
			access_show_hidden_entities($hidden);
1750 1
			elgg_set_ignore_access($ia);
1751
		}
1752
1753 1
		$this->disableMetadata();
1754 1
		$this->disableAnnotations();
1755
1756 1
		_elgg_services()->entityCache->remove($guid);
1757 1
		_elgg_get_memcache('new_entity_cache')->delete($guid);
1758
		
1759
		$sql = "
1760 1
			UPDATE {$dbprefix}entities
1761
			SET enabled = 'no'
1762
			WHERE guid = :guid
1763
		";
1764
		$params = [
1765 1
			':guid' => $guid,
1766
		];
1767 1
		$disabled = $this->getDatabase()->updateData($sql, false, $params);
1768
1769 1
		if ($unban_after) {
1770
			_elgg_services()->usersTable->markBanned($this->guid, false);
1771
		}
1772
1773 1
		if ($disabled) {
1774 1
			$this->attributes['enabled'] = 'no';
1775 1
			_elgg_services()->events->trigger('disable:after', $this->type, $this);
1776
		}
1777
1778 1
		return (bool) $disabled;
1779
	}
1780
1781
	/**
1782
	 * Enable the entity
1783
	 *
1784
	 * @warning Disabled entities can't be loaded unless
1785
	 * {@link access_show_hidden_entities(true)} has been called.
1786
	 *
1787
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
1788
	 * @see access_show_hiden_entities()
1789
	 * @return bool
1790
	 */
1791
	public function enable($recursive = true) {
1792
		$guid = (int) $this->guid;
1793
		if (!$guid) {
1794
			return false;
1795
		}
1796
		
1797
		if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
1798
			return false;
1799
		}
1800
		
1801
		if (!$this->canEdit()) {
1802
			return false;
1803
		}
1804
		
1805
		global $CONFIG;
1806
	
1807
		// Override access only visible entities
1808
		$old_access_status = access_get_show_hidden_status();
1809
		access_show_hidden_entities(true);
1810
	
1811
		$result = $this->getDatabase()->updateData("UPDATE {$CONFIG->dbprefix}entities
1812
			SET enabled = 'yes'
1813
			WHERE guid = $guid");
1814
1815
		$this->deleteMetadata('disable_reason');
1816
		$this->enableMetadata();
1817
		$this->enableAnnotations();
1818
1819
		if ($recursive) {
1820
			$disabled_with_it = elgg_get_entities_from_relationship([
1821
				'relationship' => 'disabled_with',
1822
				'relationship_guid' => $guid,
1823
				'inverse_relationship' => true,
1824
				'limit' => 0,
1825
			]);
1826
1827
			foreach ($disabled_with_it as $e) {
1828
				$e->enable();
1829
				remove_entity_relationship($e->guid, 'disabled_with', $guid);
1830
			}
1831
		}
1832
	
1833
		access_show_hidden_entities($old_access_status);
1834
	
1835
		if ($result) {
1836
			$this->attributes['enabled'] = 'yes';
1837
			_elgg_services()->events->trigger('enable:after', $this->type, $this);
1838
		}
1839
1840
		return $result;
1841
	}
1842
1843
	/**
1844
	 * Is this entity enabled?
1845
	 *
1846
	 * @return boolean Whether this entity is enabled.
1847
	 */
1848 1
	public function isEnabled() {
1849 1
		return $this->enabled == 'yes';
1850
	}
1851
1852
	/**
1853
	 * Deletes the entity.
1854
	 *
1855
	 * Removes the entity and its metadata, annotations, relationships,
1856
	 * river entries, and private data.
1857
	 *
1858
	 * Optionally can remove entities contained and owned by this entity.
1859
	 *
1860
	 * @warning If deleting recursively, this bypasses ownership of items contained by
1861
	 * the entity.  That means that if the container_guid = $this->guid, the item will
1862
	 * be deleted regardless of who owns it.
1863
	 *
1864
	 * @param bool $recursive If true (default) then all entities which are
1865
	 *                        owned or contained by $this will also be deleted.
1866
	 *
1867
	 * @return bool
1868
	 */
1869 1
	public function delete($recursive = true) {
1870
1871 1
		$guid = $this->guid;
1872 1
		if (!$guid) {
1873
			return false;
1874
		}
1875
		
1876
		// first check if we can delete this entity
1877
		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
1878
		// which could potentially remove some content if the user didn't have access
1879 1
		if (!$this->canDelete()) {
1880
			return false;
1881
		}
1882
1883
		// now trigger an event to let others know this entity is about to be deleted
1884
		// so they can prevent it or take their own actions
1885 1
		if (!_elgg_services()->events->trigger('delete', $this->type, $this)) {
1886
			return false;
1887
		}
1888
1889 1
		if ($this instanceof ElggUser) {
1890
			// ban to prevent using the site during delete
1891
			_elgg_services()->usersTable->markBanned($this->guid, true);
1892
		}
1893
1894
		// Delete contained owned and otherwise releated objects (depth first)
1895 1
		if ($recursive) {
1896
			// Temporarily overriding access controls
1897 1
			$entity_disable_override = access_get_show_hidden_status();
1898 1
			access_show_hidden_entities(true);
1899 1
			$ia = elgg_set_ignore_access(true);
1900
1901
			// @todo there was logic in the original code that ignored
1902
			// entities with owner or container guids of themselves.
1903
			// this should probably be prevented in \ElggEntity instead of checked for here
1904
			$base_options = [
1905 1
				'wheres' => [
1906 1
					"e.guid != $guid",
1907
				],
1908
				'limit' => false,
1909
			];
1910
			
1911 1
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1912 1
				$options = $base_options;
1913 1
				$options[$db_column] = $guid;
1914
				
1915 1
				$batch = new \ElggBatch('elgg_get_entities', $options);
1916 1
				$batch->setIncrementOffset(false);
1917
				
1918
				/* @var $e \ElggEntity */
1919 1
				foreach ($batch as $e) {
1920 1
					$e->delete(true);
1921
				}
1922
			}
1923
			
1924 1
			access_show_hidden_entities($entity_disable_override);
1925 1
			elgg_set_ignore_access($ia);
1926
		}
1927
1928 1
		$entity_disable_override = access_get_show_hidden_status();
1929 1
		access_show_hidden_entities(true);
1930 1
		$ia = elgg_set_ignore_access(true);
1931
		
1932
		// Now delete the entity itself
1933 1
		$this->deleteMetadata();
1934 1
		$this->deleteOwnedMetadata();
1935 1
		$this->deleteAnnotations();
1936 1
		$this->deleteOwnedAnnotations();
1937 1
		$this->deleteRelationships();
1938 1
		$this->deleteAccessCollectionMemberships();
1939 1
		$this->deleteOwnedAccessCollections();
1940
1941 1
		access_show_hidden_entities($entity_disable_override);
1942 1
		elgg_set_ignore_access($ia);
1943
1944 1
		elgg_delete_river(['subject_guid' => $guid, 'limit' => false]);
1945 1
		elgg_delete_river(['object_guid' => $guid, 'limit' => false]);
1946 1
		elgg_delete_river(['target_guid' => $guid, 'limit' => false]);
1947
		
1948 1
		remove_all_private_settings($guid);
1949
1950 1
		_elgg_invalidate_cache_for_entity($guid);
1951 1
		_elgg_invalidate_memcache_for_entity($guid);
1952
1953 1
		$dbprefix = _elgg_config()->dbprefix;
1954
		
1955
		$sql = "
1956 1
			DELETE FROM {$dbprefix}entities
1957
			WHERE guid = :guid
1958
		";
1959
		$params = [
1960 1
			':guid' => $guid,
1961
		];
1962
1963 1
		$deleted = $this->getDatabase()->deleteData($sql, $params);
1964
1965 1
		if ($deleted && in_array($this->type, ['object', 'user', 'group', 'site'])) {
1966
			// delete from type-specific subtable
1967
			$sql = "
1968 1
				DELETE FROM {$dbprefix}{$this->type}s_entity
1969
				WHERE guid = :guid
1970
			";
1971 1
			$this->getDatabase()->deleteData($sql, $params);
1972
		}
1973
		
1974 1
		_elgg_clear_entity_files($this);
1975
1976 1
		return (bool) $deleted;
1977
	}
1978
1979
	/**
1980
	 * {@inheritdoc}
1981
	 */
1982 2 View Code Duplication
	public function toObject() {
1983 2
		$object = $this->prepareObject(new \stdClass());
1984 2
		$params = ['entity' => $this];
1985 2
		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1986 2
		return $object;
1987
	}
1988
1989
	/**
1990
	 * Prepare an object copy for toObject()
1991
	 *
1992
	 * @param \stdClass $object Object representation of the entity
1993
	 * @return \stdClass
1994
	 */
1995 2
	protected function prepareObject($object) {
1996 2
		$object->guid = $this->guid;
1997 2
		$object->type = $this->getType();
1998 2
		$object->subtype = $this->getSubtype();
1999 2
		$object->owner_guid = $this->getOwnerGUID();
2000 2
		$object->container_guid = $this->getContainerGUID();
2001 2
		$object->time_created = date('c', $this->getTimeCreated());
2002 2
		$object->time_updated = date('c', $this->getTimeUpdated());
2003 2
		$object->url = $this->getURL();
2004 2
		$object->read_access = (int) $this->access_id;
2005 2
		return $object;
2006
	}
2007
2008
	/*
2009
	 * LOCATABLE INTERFACE
2010
	 */
2011
2012
	/**
2013
	 * Gets the 'location' metadata for the entity
2014
	 *
2015
	 * @return string The location
2016
	 */
2017
	public function getLocation() {
2018
		return $this->location;
2019
	}
2020
2021
	/**
2022
	 * Sets the 'location' metadata for the entity
2023
	 *
2024
	 * @param string $location String representation of the location
2025
	 *
2026
	 * @return void
2027
	 */
2028
	public function setLocation($location) {
2029
		$this->location = $location;
2030
	}
2031
2032
	/**
2033
	 * Set latitude and longitude metadata tags for a given entity.
2034
	 *
2035
	 * @param float $lat  Latitude
2036
	 * @param float $long Longitude
2037
	 *
2038
	 * @return void
2039
	 * @todo Unimplemented
2040
	 */
2041 1
	public function setLatLong($lat, $long) {
2042 1
		$this->{"geo:lat"} = $lat;
2043 1
		$this->{"geo:long"} = $long;
2044 1
	}
2045
2046
	/**
2047
	 * Return the entity's latitude.
2048
	 *
2049
	 * @return float
2050
	 * @todo Unimplemented
2051
	 */
2052 1
	public function getLatitude() {
2053 1
		return (float) $this->{"geo:lat"};
2054
	}
2055
2056
	/**
2057
	 * Return the entity's longitude
2058
	 *
2059
	 * @return float
2060
	 * @todo Unimplemented
2061
	 */
2062 1
	public function getLongitude() {
2063 1
		return (float) $this->{"geo:long"};
2064
	}
2065
2066
	/*
2067
	 * SYSTEM LOG INTERFACE
2068
	 */
2069
2070
	/**
2071
	 * Return an identification for the object for storage in the system log.
2072
	 * This id must be an integer.
2073
	 *
2074
	 * @return int
2075
	 */
2076
	public function getSystemLogID() {
2077
		return $this->getGUID();
2078
	}
2079
2080
	/**
2081
	 * For a given ID, return the object associated with it.
2082
	 * This is used by the system log. It can be called on any Loggable object.
2083
	 *
2084
	 * @param int $id GUID.
2085
	 * @return int GUID
2086
	 */
2087
	public function getObjectFromID($id) {
2088
		return get_entity($id);
2089
	}
2090
2091
	/**
2092
	 * Returns tags for this entity.
2093
	 *
2094
	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
2095
	 *
2096
	 * @param array $tag_names Optionally restrict by tag metadata names.
2097
	 *
2098
	 * @return array
2099
	 */
2100
	public function getTags($tag_names = null) {
2101
		if ($tag_names && !is_array($tag_names)) {
2102
			$tag_names = [$tag_names];
2103
		}
2104
2105
		$valid_tags = elgg_get_registered_tag_metadata_names();
2106
		$entity_tags = [];
2107
2108
		foreach ($valid_tags as $tag_name) {
2109
			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
2110
				continue;
2111
			}
2112
2113
			if ($tags = $this->$tag_name) {
2114
				// if a single tag, metadata returns a string.
2115
				// if multiple tags, metadata returns an array.
2116
				if (is_array($tags)) {
2117
					$entity_tags = array_merge($entity_tags, $tags);
2118
				} else {
2119
					$entity_tags[] = $tags;
2120
				}
2121
			}
2122
		}
2123
2124
		return $entity_tags;
2125
	}
2126
	
2127
	/**
2128
	 * Remove the membership of all access collections for this entity (if the entity is a user)
2129
	 *
2130
	 * @return bool
2131
	 * @since 1.11
2132
	 */
2133 1
	public function deleteAccessCollectionMemberships() {
2134
	
2135 1
		if (!$this->guid) {
2136
			return false;
2137
		}
2138
		
2139 1
		if ($this->type !== 'user') {
2140 1
			return true;
2141
		}
2142
		
2143
		$ac = _elgg_services()->accessCollections;
2144
		
2145
		$collections = $ac->getCollectionsByMember($this->guid);
2146
		if (empty($collections)) {
2147
			return true;
2148
		}
2149
		
2150
		$result = true;
2151
		foreach ($collections as $collection) {
2152
			$result = $result & $ac->removeUser($this->guid, $collection->id);
2153
		}
2154
		
2155
		return $result;
2156
	}
2157
	
2158
	/**
2159
	 * Remove all access collections owned by this entity
2160
	 *
2161
	 * @return bool
2162
	 * @since 1.11
2163
	 */
2164 1
	public function deleteOwnedAccessCollections() {
2165
		
2166 1
		if (!$this->guid) {
2167
			return false;
2168
		}
2169
		
2170 1
		$ac = _elgg_services()->accessCollections;
2171
		
2172 1
		$collections = $ac->getEntityCollections($this->guid);
2173 1
		if (empty($collections)) {
2174 1
			return true;
2175
		}
2176
		
2177
		$result = true;
2178
		foreach ($collections as $collection) {
2179
			$result = $result & $ac->delete($collection->id);
2180
		}
2181
		
2182
		return $result;
2183
	}
2184
2185
	/**
2186
	 * Update the last_action column in the entities table.
2187
	 *
2188
	 * @warning This is different to time_updated.  Time_updated is automatically set,
2189
	 * while last_action is only set when explicitly called.
2190
	 *
2191
	 * @param int $posted Timestamp of last action
2192
	 * @return int|false
2193
	 * @access private
2194
	 */
2195 2
	public function updateLastAction($posted = null) {
2196 2
		$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2197 2
		if ($posted) {
2198 2
			$this->attributes['last_action'] = $posted;
2199 2
			_elgg_services()->entityCache->set($this);
2200 2
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
2201
		}
2202 2
		return $posted;
2203
	}
2204
}
2205