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

engine/classes/ElggEntity.php (2 issues)

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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guid of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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']);
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) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $posted of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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