Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/ElggEntity.php (11 issues)

1
<?php
2
3
use Elgg\EntityIcon;
4
5
/**
6
 * The parent class for all Elgg Entities.
7
 *
8
 * An \ElggEntity is one of the basic data models in Elgg.
9
 * It is the primary means of storing and retrieving data from the database.
10
 * An \ElggEntity represents one row of the entities table.
11
 *
12
 * The \ElggEntity class handles CRUD operations for the entities table.
13
 * \ElggEntity should always be extended by another class to handle CRUD
14
 * operations on the type-specific table.
15
 *
16
 * \ElggEntity uses magic methods for get and set, so any property that isn't
17
 * declared will be assumed to be metadata and written to the database
18
 * as metadata on the object.  All children classes must declare which
19
 * properties are columns of the type table or they will be assumed
20
 * to be metadata.  See \ElggObject::initializeAttributes() for examples.
21
 *
22
 * Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and \ElggSite.
23
 *
24
 * @tip Plugin authors will want to extend the \ElggObject class, not this class.
25
 *
26
 * @package    Elgg.Core
27
 * @subpackage DataModel.Entities
28
 *
29
 * @property       string $type           object, user, group, or site (read-only after save)
30
 * @property       string $subtype        Further clarifies the nature of the entity (this should not be read)
31
 * @property-read  int    $guid           The unique identifier for this entity (read only)
32
 * @property       int    $owner_guid     The GUID of the owner of this entity (usually the creator)
33
 * @property       int    $container_guid The GUID of the entity containing this entity
34
 * @property       int    $access_id      Specifies the visibility level of this entity
35
 * @property       int    $time_created   A UNIX timestamp of when the entity was created
36
 * @property-read  int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save)
37
 * @property-read  int    $last_action    A UNIX timestamp of when the entity was last acted upon
38
 * @property       string $enabled        Is this entity enabled ('yes' or 'no')
39
 *
40
 * Metadata (the above are attributes)
41
 * @property       string $location       A location of the entity
42
 */
43
abstract class ElggEntity extends \ElggData implements
44
	Locatable, // Geocoding interface
45
	EntityIcon // Icon interface
46
{
47
48
	public static $primary_attr_names = [
49
		'guid',
50
		'type',
51
		'subtype',
52
		'owner_guid',
53
		'container_guid',
54
		'access_id',
55
		'time_created',
56
		'time_updated',
57
		'last_action',
58
		'enabled',
59
	];
60
61
	protected static $integer_attr_names = [
62
		'guid',
63
		'owner_guid',
64
		'container_guid',
65
		'access_id',
66
		'time_created',
67
		'time_updated',
68
		'last_action',
69
	];
70
71
	/**
72
	 * Holds metadata until entity is saved.  Once the entity is saved,
73
	 * metadata are written immediately to the database.
74
	 * @var array
75
	 */
76
	protected $temp_metadata = [];
77
78
	/**
79
	 * Holds annotations until entity is saved.  Once the entity is saved,
80
	 * annotations are written immediately to the database.
81
	 * @var array
82
	 */
83
	protected $temp_annotations = [];
84
85
	/**
86
	 * Holds private settings until entity is saved. Once the entity is saved,
87
	 * private settings are written immediately to the database.
88
	 * @var array
89
	 */
90
	protected $temp_private_settings = [];
91
92
	/**
93
	 * Volatile data structure for this object, allows for storage of data
94
	 * in-memory that isn't sync'd back to the metadata table.
95
	 * @var array
96
	 */
97
	protected $volatile = [];
98
99
	/**
100
	 * Holds the original (persisted) attribute values that have been changed but not yet saved.
101
	 * @var array
102
	 */
103
	protected $orig_attributes = [];
104
105
	/**
106
	 * @var bool
107
	 */
108
	protected $_is_cacheable = true;
109
110
	/**
111
	 * Create a new entity.
112
	 *
113
	 * Plugin developers should only use the constructor to create a new entity.
114
	 * To retrieve entities, use get_entity() and the elgg_get_entities* functions.
115
	 *
116
	 * If no arguments are passed, it creates a new entity.
117
	 * If a database result is passed as a \stdClass instance, it instantiates
118
	 * that entity.
119
	 *
120
	 * @param stdClass $row Database row result. Default is null to create a new object.
121
	 *
122
	 * @throws IOException If cannot load remaining data from db
123
	 */
124 5322
	public function __construct(stdClass $row = null) {
125 5322
		$this->initializeAttributes();
126
127 5322
		if ($row && !$this->load($row)) {
128
			$msg = "Failed to load new " . get_class() . " for GUID:" . $row->guid;
129
			throw new \IOException($msg);
130
		}
131 5322
	}
132
133
	/**
134
	 * Initialize the attributes array.
135
	 *
136
	 * This is vital to distinguish between metadata and base parameters.
137
	 *
138
	 * @return void
139
	 */
140 5322
	protected function initializeAttributes() {
141 5322
		parent::initializeAttributes();
142
143 5322
		$this->attributes['guid'] = null;
144 5322
		$this->attributes['type'] = null;
145 5322
		$this->attributes['subtype'] = null;
146
147 5322
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
148 5322
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
149
150 5322
		$this->attributes['access_id'] = ACCESS_PRIVATE;
151 5322
		$this->attributes['time_updated'] = null;
152 5322
		$this->attributes['last_action'] = null;
153 5322
		$this->attributes['enabled'] = "yes";
154
155 5322
		$this->attributes['type'] = $this->getType();
156 5322
	}
157
158
	/**
159
	 * Clone an entity
160
	 *
161
	 * Resets the guid so that the entity can be saved as a distinct entity from
162
	 * the original. Creation time will be set when this new entity is saved.
163
	 * The owner and container guids come from the original entity. The clone
164
	 * method copies metadata but does not copy annotations or private settings.
165
	 *
166
	 * @note metadata will have its owner and access id set when the entity is saved
167
	 * and it will be the same as that of the entity.
168
	 *
169
	 * @return void
170
	 */
171 1
	public function __clone() {
172 1
		$orig_entity = get_entity($this->guid);
173 1
		if (!$orig_entity) {
174
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
175
			return;
176
		}
177
178 1
		$metadata_array = elgg_get_metadata([
179 1
			'guid' => $this->guid,
180 1
			'limit' => 0
181
		]);
182
183 1
		$this->attributes['guid'] = null;
184 1
		$this->attributes['time_created'] = null;
185 1
		$this->attributes['time_updated'] = null;
186 1
		$this->attributes['last_action'] = null;
187
188 1
		$this->attributes['subtype'] = $orig_entity->getSubtype();
189
190
		// copy metadata over to new entity - slightly convoluted due to
191
		// handling of metadata arrays
192 1
		if (is_array($metadata_array)) {
193
			// create list of metadata names
194 1
			$metadata_names = [];
195 1
			foreach ($metadata_array as $metadata) {
196 1
				$metadata_names[] = $metadata['name'];
197
			}
198
			// arrays are stored with multiple enties per name
199 1
			$metadata_names = array_unique($metadata_names);
200
201
			// move the metadata over
202 1
			foreach ($metadata_names as $name) {
203 1
				$this->__set($name, $orig_entity->$name);
204
			}
205
		}
206 1
	}
207
208
	/**
209
	 * Set an attribute or metadata value for this entity
210
	 *
211
	 * Anything that is not an attribute is saved as metadata.
212
	 *
213
	 * @warning Metadata set this way will inherit the entity's owner and
214
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
215
	 *
216
	 * @param string $name  Name of the attribute or metadata
217
	 * @param mixed  $value The value to be set
218
	 * @return void
219
	 * @see \ElggEntity::setMetadata()
220
	 */
221 1092
	public function __set($name, $value) {
222 1092
		if ($this->$name === $value) {
223
			// quick return if value is not changing
224 289
			return;
225
		}
226
227
		// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
228
		// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
229
		// the value to empty string, don't consider this a change.
230 1092
		if (in_array($name, ['title', 'name', 'description'])
231 1092
			&& $this->$name === null
232 1092
			&& $value === "") {
233
			return;
234
		}
235
236 1092
		if (array_key_exists($name, $this->attributes)) {
237
			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
238 589
			if (is_int($this->attributes[$name])
239 589
					&& is_string($value)
240 589
					&& ((string) $this->attributes[$name] === $value)) {
241 1
				return;
242
			}
243
244
			// keep original values
245 589
			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
246 9
				$this->orig_attributes[$name] = $this->attributes[$name];
247
			}
248
249
			// Certain properties should not be manually changed!
250 589
			switch ($name) {
251
				case 'guid':
252
				case 'time_updated':
253
				case 'last_action':
254
					return;
255
					break;
256
				case 'access_id':
257
				case 'owner_guid':
258
				case 'container_guid':
259 482
					if ($value !== null) {
260 482
						$this->attributes[$name] = (int) $value;
261
					} else {
262
						$this->attributes[$name] = null;
263
					}
264 482
					break;
265
				default:
266 443
					$this->attributes[$name] = $value;
267 443
					break;
268
			}
269 589
			return;
270
		}
271
272 1062
		$this->setMetadata($name, $value);
273 1062
	}
274
275
	/**
276
	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
277
	 *
278
	 * @return array
279
	 */
280 59
	public function getOriginalAttributes() {
281 59
		return $this->orig_attributes;
282
	}
283
284
	/**
285
	 * Get an attribute or metadata value
286
	 *
287
	 * If the name matches an attribute, the attribute is returned. If metadata
288
	 * does not exist with that name, a null is returned.
289
	 *
290
	 * This only returns an array if there are multiple values for a particular
291
	 * $name key.
292
	 *
293
	 * @param string $name Name of the attribute or metadata
294
	 * @return mixed
295
	 */
296 5387
	public function __get($name) {
297 5387
		if (array_key_exists($name, $this->attributes)) {
298 5386
			return $this->attributes[$name];
299
		}
300
301 5363
		return $this->getMetadata($name);
302
	}
303
304
	/**
305
	 * Get the entity's display name
306
	 *
307
	 * @return string The title or name of this entity.
308
	 */
309 19
	public function getDisplayName() {
310 19
		return $this->name;
311
	}
312
313
	/**
314
	 * Sets the title or name of this entity.
315
	 *
316
	 * @param string $display_name The title or name of this entity.
317
	 * @return void
318
	 */
319
	public function setDisplayName($display_name) {
320
		$this->name = $display_name;
1 ignored issue
show
Bug Best Practice introduced by
The property name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
321
	}
322
323
	/**
324
	 * Return the value of a piece of metadata.
325
	 *
326
	 * @param string $name Name
327
	 *
328
	 * @return mixed The value, or null if not found.
329
	 */
330 5363
	public function getMetadata($name) {
331 5363
		$guid = $this->guid;
332
333 5363
		if (!$guid) {
334 572
			if (isset($this->temp_metadata[$name])) {
335
				// md is returned as an array only if more than 1 entry
336 285
				if (count($this->temp_metadata[$name]) == 1) {
337 285
					return $this->temp_metadata[$name][0];
338
				} else {
339 158
					return $this->temp_metadata[$name];
340
				}
341
			} else {
342 572
				return null;
343
			}
344
		}
345
346
		// upon first cache miss, just load/cache all the metadata and retry.
347
		// if this works, the rest of this function may not be needed!
348 5354
		$cache = _elgg_services()->metadataCache;
349 5354
		if ($cache->isLoaded($guid)) {
350 2238
			return $cache->getSingle($guid, $name);
351
		} else {
352 4136
			$cache->populateFromEntities([$guid]);
353
			// in case ignore_access was on, we have to check again...
354 4136
			if ($cache->isLoaded($guid)) {
355 1082
				return $cache->getSingle($guid, $name);
356
			}
357
		}
358
359 4083
		$md = elgg_get_metadata([
360 4083
			'guid' => $guid,
361 4083
			'metadata_name' => $name,
362 4083
			'limit' => 0,
363
			'distinct' => false,
364
		]);
365
366 4083
		$value = null;
367
368 4083
		if ($md && !is_array($md)) {
369
			$value = $md->value;
0 ignored issues
show
The property value does not exist on integer.
Loading history...
370 4083
		} elseif (count($md) == 1) {
371
			$value = $md[0]->value;
372 4083
		} else if ($md && is_array($md)) {
373
			$value = metadata_array_to_values($md);
374
		}
375
376 4083
		return $value;
377
	}
378
379
	/**
380
	 * Unset a property from metadata or attribute.
381
	 *
382
	 * @warning If you use this to unset an attribute, you must save the object!
383
	 *
384
	 * @param string $name The name of the attribute or metadata.
385
	 *
386
	 * @return void
387
	 * @todo some attributes should be set to null or other default values
388
	 */
389 49
	public function __unset($name) {
390 49
		if (array_key_exists($name, $this->attributes)) {
391
			$this->attributes[$name] = "";
392
		} else {
393 49
			$this->deleteMetadata($name);
394
		}
395 49
	}
396
397
	/**
398
	 * Set metadata on this entity.
399
	 *
400
	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
401
	 * Use this method if you want to explicitly set the owner or access of the metadata.
402
	 * You cannot set the owner/access before the entity has been saved.
403
	 *
404
	 * @param string $name       Name of the metadata
405
	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
406
	 * @param string $value_type 'text', 'integer', or '' for automatic detection
407
	 * @param bool   $multiple   Allow multiple values for a single name.
408
	 *                           Does not support associative arrays.
409
	 *
410
	 * @return bool
411
	 * @throws InvalidArgumentException
412
	 */
413 1062
	public function setMetadata($name, $value, $value_type = '', $multiple = false) {
414
415
		// normalize value to an array that we will loop over
416
		// remove indexes if value already an array.
417 1062
		if (is_array($value)) {
418 162
			$value = array_values($value);
419
		} else {
420 1061
			$value = [$value];
421
		}
422
423
		// strip null values from array
424 1062
		$value = array_filter($value, function($var) {
425 1062
			return !is_null($var);
426 1062
		});
427
428 1062
		if (empty($this->guid)) {
429
			// unsaved entity. store in temp array
430 569
			return $this->setTempMetadata($name, $value, $multiple);
431
		}
432
433
		// saved entity. persist md to db.
434 1028
		if (!$multiple) {
435 1028
			$current_metadata = $this->getMetadata($name);
436
437 1028
			if ((is_array($current_metadata) || count($value) > 1 || $value === []) && isset($current_metadata)) {
438
				// remove current metadata if needed
439
				// need to remove access restrictions right now to delete
440
				// because this is the expected behavior
441 8
				$ia = elgg_set_ignore_access(true);
442 8
				$delete_result = elgg_delete_metadata([
443 8
					'guid' => $this->guid,
444 8
					'metadata_name' => $name,
445
					'limit' => false,
446
				]);
447 8
				elgg_set_ignore_access($ia);
448
449 8
				if (false === $delete_result) {
450
					return false;
451
				}
452
			}
453
454 1028
			if (count($value) > 1) {
455
				// new value is a multiple valued metadata
456 162
				$multiple = true;
457
			}
458
		}
459
460
		// create new metadata
461 1028
		foreach ($value as $value_tmp) {
462 1028
			$metadata = new ElggMetadata();
463 1028
			$metadata->entity_guid = $this->guid;
464 1028
			$metadata->name = $name;
465 1028
			$metadata->value_type = $value_type;
466 1028
			$metadata->value = $value_tmp;
467 1028
			$md_id = _elgg_services()->metadataTable->create($metadata, $multiple);
468 1028
			if (!$md_id) {
469 1028
				return false;
470
			}
471
		}
472
473 1028
		return true;
474
	}
475
476
	/**
477
	 * Set temp metadata on this entity.
478
	 *
479
	 * @param string $name     Name of the metadata
480
	 * @param mixed  $value    Value of the metadata (doesn't support assoc arrays)
481
	 * @param bool   $multiple Allow multiple values for a single name.
482
	 *                         Does not support associative arrays.
483
	 *
484
	 * @return bool
485
	 */
486 569
	protected function setTempMetadata($name, $value, $multiple = false) {
487
		// if overwrite, delete first
488 569
		if (!$multiple) {
489 569
			unset($this->temp_metadata[$name]);
490 569
			if (count($value)) {
491
				// only save if value array contains data
492 569
				$this->temp_metadata[$name] = $value;
493
			}
494 569
			return true;
495
		}
496
497 3
		if (!isset($this->temp_metadata[$name])) {
498
			$this->temp_metadata[$name] = [];
499
		}
500
501 3
		$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
502
503 3
		return true;
504
	}
505
506
507
508
	/**
509
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
510
	 * If you pass a name, only metadata matching that name will be deleted.
511
	 *
512
	 * @warning Calling this with no $name will clear all metadata on the entity.
513
	 *
514
	 * @param null|string $name The name of the metadata to remove.
515
	 * @return bool|null
516
	 * @since 1.8
517
	 */
518 266
	public function deleteMetadata($name = null) {
519
520 266
		if (!$this->guid) {
521
			// remove from temp_metadata
522 2
			if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
523 2
				if (!isset($this->temp_metadata[$name])) {
524
					return null;
525
				} else {
526 2
					unset($this->temp_metadata[$name]);
527 2
					return true;
528
				}
529
			} else {
530
				$this->temp_metadata = [];
531
				return true;
532
			}
533
		}
534
535
		$options = [
536 266
			'guid' => $this->guid,
537 266
			'limit' => 0
538
		];
539 266
		if ($name) {
540 55
			$options['metadata_name'] = $name;
541
		}
542
543 266
		return elgg_delete_metadata($options);
544
	}
545
546
	/**
547
	 * Get a piece of volatile (non-persisted) data on this entity.
548
	 *
549
	 * @param string $name The name of the volatile data
550
	 *
551
	 * @return mixed The value or null if not found.
552
	 */
553 25
	public function getVolatileData($name) {
554 25
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
555
	}
556
557
	/**
558
	 * Set a piece of volatile (non-persisted) data on this entity
559
	 *
560
	 * @param string $name  Name
561
	 * @param mixed  $value Value
562
	 *
563
	 * @return void
564
	 */
565 26
	public function setVolatileData($name, $value) {
566 26
		$this->volatile[$name] = $value;
567 26
	}
568
569
	/**
570
	 * Remove all relationships to and from this entity.
571
	 * If you pass a relationship name, only relationships matching that name
572
	 * will be deleted.
573
	 *
574
	 * @warning Calling this with no $relationship will clear all relationships
575
	 * for this entity.
576
	 *
577
	 * @param null|string $relationship The name of the relationship to remove.
578
	 * @return bool
579
	 * @see \ElggEntity::addRelationship()
580
	 * @see \ElggEntity::removeRelationship()
581
	 */
582 214
	public function deleteRelationships($relationship = null) {
583 214
		$relationship = (string) $relationship;
584 214
		$result = remove_entity_relationships($this->getGUID(), $relationship);
585 214
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
586
	}
587
588
	/**
589
	 * Add a relationship between this an another entity.
590
	 *
591
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
592
	 *
593
	 * @param int    $guid_two     GUID of the target entity of the relationship.
594
	 * @param string $relationship The type of relationship.
595
	 *
596
	 * @return bool
597
	 * @see \ElggEntity::removeRelationship()
598
	 * @see \ElggEntity::deleteRelationships()
599
	 */
600 5
	public function addRelationship($guid_two, $relationship) {
601 5
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
602
	}
603
604
	/**
605
	 * Remove a relationship
606
	 *
607
	 * @param int    $guid_two     GUID of the target entity of the relationship.
608
	 * @param string $relationship The type of relationship.
609
	 *
610
	 * @return bool
611
	 * @see \ElggEntity::addRelationship()
612
	 * @see \ElggEntity::deleteRelationships()
613
	 */
614 1
	public function removeRelationship($guid_two, $relationship) {
615 1
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
616
	}
617
618
	/**
619
	 * Adds a private setting to this entity.
620
	 *
621
	 * Private settings are similar to metadata but will not
622
	 * be searched and there are fewer helper functions for them.
623
	 *
624
	 * @param string $name  Name of private setting
625
	 * @param mixed  $value Value of private setting
626
	 *
627
	 * @return bool
628
	 */
629 124
	public function setPrivateSetting($name, $value) {
630 124
		if ((int) $this->guid > 0) {
631 123
			return set_private_setting($this->getGUID(), $name, $value);
632
		} else {
633 45
			$this->temp_private_settings[$name] = $value;
634 45
			return true;
635
		}
636
	}
637
638
	/**
639
	 * Returns a private setting value
640
	 *
641
	 * @param string $name Name of the private setting
642
	 *
643
	 * @return mixed Null if the setting does not exist
644
	 */
645 37
	public function getPrivateSetting($name) {
646 37
		if ((int) ($this->guid) > 0) {
647 37
			return get_private_setting($this->getGUID(), $name);
648
		} else {
649 5
			if (isset($this->temp_private_settings[$name])) {
650 5
				return $this->temp_private_settings[$name];
651
			}
652
		}
653 5
		return null;
654
	}
655
656
	/**
657
	 * Removes private setting
658
	 *
659
	 * @param string $name Name of the private setting
660
	 *
661
	 * @return bool
662
	 */
663 3
	public function removePrivateSetting($name) {
664 3
		return remove_private_setting($this->getGUID(), $name);
665
	}
666
667
	/**
668
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
669
	 * If you pass a name, only annotations matching that name will be deleted.
670
	 *
671
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
672
	 *
673
	 * @param null|string $name The annotations name to remove.
674
	 * @return bool
675
	 * @since 1.8
676
	 */
677 214
	public function deleteAnnotations($name = null) {
678
		$options = [
679 214
			'guid' => $this->guid,
680 214
			'limit' => 0
681
		];
682 214
		if ($name) {
683 1
			$options['annotation_name'] = $name;
684
		}
685
686 214
		return elgg_delete_annotations($options);
687
	}
688
689
	/**
690
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
691
	 * If you pass a name, only annotations matching that name will be deleted.
692
	 *
693
	 * @param null|string $name The name of annotations to delete.
694
	 * @return bool
695
	 * @since 1.8
696
	 */
697 214
	public function deleteOwnedAnnotations($name = null) {
698
		// access is turned off for this because they might
699
		// no longer have access to an entity they created annotations on.
700 214
		$ia = elgg_set_ignore_access(true);
701
		$options = [
702 214
			'annotation_owner_guid' => $this->guid,
703 214
			'limit' => 0
704
		];
705 214
		if ($name) {
706
			$options['annotation_name'] = $name;
707
		}
708
709 214
		$r = elgg_delete_annotations($options);
710 214
		elgg_set_ignore_access($ia);
711 214
		return $r;
712
	}
713
714
	/**
715
	 * Disables annotations for this entity, optionally based on name.
716
	 *
717
	 * @param string $name An options name of annotations to disable.
718
	 * @return bool
719
	 * @since 1.8
720
	 */
721 5
	public function disableAnnotations($name = '') {
722
		$options = [
723 5
			'guid' => $this->guid,
724 5
			'limit' => 0
725
		];
726 5
		if ($name) {
727
			$options['annotation_name'] = $name;
728
		}
729
730 5
		return elgg_disable_annotations($options);
731
	}
732
733
	/**
734
	 * Enables annotations for this entity, optionally based on name.
735
	 *
736
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
737
	 *
738
	 * @param string $name An options name of annotations to enable.
739
	 * @return bool
740
	 * @since 1.8
741
	 */
742 3
	public function enableAnnotations($name = '') {
743
		$options = [
744 3
			'guid' => $this->guid,
745 3
			'limit' => 0
746
		];
747 3
		if ($name) {
748
			$options['annotation_name'] = $name;
749
		}
750
751 3
		return elgg_enable_annotations($options);
752
	}
753
754
	/**
755
	 * Helper function to return annotation calculation results
756
	 *
757
	 * @param string $name        The annotation name.
758
	 * @param string $calculation A valid MySQL function to run its values through
759
	 * @return mixed
760
	 */
761 2
	private function getAnnotationCalculation($name, $calculation) {
762
		$options = [
763 2
			'guid' => $this->getGUID(),
764
			'distinct' => false,
765 2
			'annotation_name' => $name,
766 2
			'annotation_calculation' => $calculation
767
		];
768
769 2
		return elgg_get_annotations($options);
770
	}
771
772
	/**
773
	 * Adds an annotation to an entity.
774
	 *
775
	 * @warning By default, annotations are private.
776
	 *
777
	 * @warning Annotating an unsaved entity more than once with the same name
778
	 *          will only save the last annotation.
779
	 *
780
	 * @todo Update temp_annotations to store an instance of ElggAnnotation and simply call ElggAnnotation::save(),
781
	 *       after entity is saved
782
	 *
783
	 * @param string $name       Annotation name
784
	 * @param mixed  $value      Annotation value
785
	 * @param int    $access_id  Access ID
786
	 * @param int    $owner_guid GUID of the annotation owner
787
	 * @param string $value_type The type of annotation value
788
	 *
789
	 * @return bool|int Returns int if an annotation is saved
790
	 */
791 93
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $value_type = "") {
792 93
		if ($this->guid) {
793 93
			if (!$owner_guid) {
794 92
				$owner_guid = elgg_get_logged_in_user_guid();
795
			}
796 93
			$annotation = new ElggAnnotation();
797 93
			$annotation->entity_guid = $this->guid;
798 93
			$annotation->name = $name;
799 93
			$annotation->value_type = $value_type;
800 93
			$annotation->value = $value;
801 93
			$annotation->owner_guid = $owner_guid;
802 93
			$annotation->access_id = $access_id;
803 93
			return $annotation->save();
804
		} else {
805 17
			$this->temp_annotations[$name] = $value;
806
		}
807 17
		return true;
808
	}
809
810
	/**
811
	 * Gets an array of annotations.
812
	 *
813
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
814
	 * as the options array.
815
	 *
816
	 * @param array $options Array of options for elgg_get_annotations() except guid.
817
	 *
818
	 * @return array
819
	 * @see elgg_get_annotations()
820
	 */
821 9
	public function getAnnotations(array $options = []) {
822 9
		if ($this->guid) {
823 9
			$options['guid'] = $this->guid;
824
825 9
			return elgg_get_annotations($options);
826
		} else {
827
			$name = elgg_extract('annotation_name', $options, '');
828
829
			if (isset($this->temp_annotations[$name])) {
830
				return [$this->temp_annotations[$name]];
831
			}
832
		}
833
834
		return [];
835
	}
836
837
	/**
838
	 * Count annotations.
839
	 *
840
	 * @param string $name The type of annotation.
841
	 *
842
	 * @return int
843
	 */
844 2
	public function countAnnotations($name = "") {
845 2
		return $this->getAnnotationCalculation($name, 'count');
846
	}
847
848
	/**
849
	 * Get the average of an integer type annotation.
850
	 *
851
	 * @param string $name Annotation name
852
	 *
853
	 * @return int
854
	 */
855
	public function getAnnotationsAvg($name) {
856
		return $this->getAnnotationCalculation($name, 'avg');
857
	}
858
859
	/**
860
	 * Get the sum of integer type annotations of a given name.
861
	 *
862
	 * @param string $name Annotation name
863
	 *
864
	 * @return int
865
	 */
866
	public function getAnnotationsSum($name) {
867
		return $this->getAnnotationCalculation($name, 'sum');
868
	}
869
870
	/**
871
	 * Get the minimum of integer type annotations of given name.
872
	 *
873
	 * @param string $name Annotation name
874
	 *
875
	 * @return int
876
	 */
877
	public function getAnnotationsMin($name) {
878
		return $this->getAnnotationCalculation($name, 'min');
879
	}
880
881
	/**
882
	 * Get the maximum of integer type annotations of a given name.
883
	 *
884
	 * @param string $name Annotation name
885
	 *
886
	 * @return int
887
	 */
888
	public function getAnnotationsMax($name) {
889
		return $this->getAnnotationCalculation($name, 'max');
890
	}
891
892
	/**
893
	 * Count the number of comments attached to this entity.
894
	 *
895
	 * @return int Number of comments
896
	 * @since 1.8.0
897
	 */
898 2
	public function countComments() {
899 2
		$params = ['entity' => $this];
900 2
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
901
902 2
		if (is_int($num)) {
903
			return $num;
904
		}
905
906 2
		return elgg_get_entities([
1 ignored issue
show
Bug Best Practice introduced by
The expression return elgg_get_entities..., 'distinct' => false)) also could return the type ElggEntity[]|false which is incompatible with the documented return type integer.
Loading history...
907 2
			'type' => 'object',
908 2
			'subtype' => 'comment',
909 2
			'container_guid' => $this->getGUID(),
910
			'count' => true,
911
			'distinct' => false,
912
		]);
913
	}
914
915
	/**
916
	 * Returns the ACLs owned by the entity
917
	 *
918
	 * @param array $options additional options to get the access collections with
919
	 *
920
	 * @return \ElggAccessCollection[]
921
	 *
922
	 * @see elgg_get_access_collections()
923
	 * @since 3.0
924
	 */
925 218
	public function getOwnedAccessCollections($options = []) {
926 218
		$options['owner_guid'] = $this->guid;
927 218
		return _elgg_services()->accessCollections->getEntityCollections($options);
928
	}
929
	
930
	/**
931
	 * Returns the first ACL owned by the entity with a given subtype
932
	 *
933
	 * @param string $subtype subtype of the ACL
934
	 *
935
	 * @return \ElggAccessCollection|false
936
	 *
937
	 * @since 3.0
938
	 */
939
	public function getOwnedAccessCollection($subtype) {
940
		if (!is_string($subtype) || $subtype === '') {
941
			return false;
942
		}
943
		
944
		$acls = $this->getOwnedAccessCollections([
945
			'subtype' => $subtype,
946
		]);
947
		
948
		return elgg_extract(0, $acls, false);
949
	}
950
951
	/**
952
	 * Gets an array of entities with a relationship to this entity.
953
	 *
954
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
955
	 *                       for a list of options. 'relationship_guid' is set to
956
	 *                       this entity.
957
	 *
958
	 * @return array|false An array of entities or false on failure
959
	 * @see elgg_get_entities_from_relationship()
960
	 */
961
	public function getEntitiesFromRelationship(array $options = []) {
962
		$options['relationship_guid'] = $this->guid;
963
		return elgg_get_entities($options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return elgg_get_entities($options) also could return the type integer which is incompatible with the documented return type false|array.
Loading history...
964
	}
965
966
	/**
967
	 * Gets the number of entities from a specific relationship type
968
	 *
969
	 * @param string $relationship         Relationship type (eg "friends")
970
	 * @param bool   $inverse_relationship Invert relationship
971
	 *
972
	 * @return int|false The number of entities or false on failure
973
	 */
974
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
975
		return elgg_get_entities([
976
			'relationship' => $relationship,
977
			'relationship_guid' => $this->getGUID(),
978
			'inverse_relationship' => $inverse_relationship,
979
			'count' => true
980
		]);
981
	}
982
983
	/**
984
	 * Can a user edit this entity?
985
	 *
986
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
987
	 *
988
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
989
	 *
990
	 * @return bool Whether this entity is editable by the given user.
991
	 * @see elgg_set_ignore_access()
992
	 */
993 399
	public function canEdit($user_guid = 0) {
994 399
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
995
	}
996
997
	/**
998
	 * Can a user delete this entity?
999
	 *
1000
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
1001
	 *
1002
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1003
	 *
1004
	 * @return bool Whether this entity is deletable by the given user.
1005
	 * @since 1.11
1006
	 * @see elgg_set_ignore_access()
1007
	 */
1008 462
	public function canDelete($user_guid = 0) {
1009 462
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
1010
	}
1011
1012
	/**
1013
	 * Can a user edit metadata on this entity?
1014
	 *
1015
	 * If no specific metadata is passed, it returns whether the user can
1016
	 * edit any metadata on the entity.
1017
	 *
1018
	 * @tip Can be overridden by by registering for the permissions_check:metadata
1019
	 * plugin hook.
1020
	 *
1021
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1022
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1023
	 *
1024
	 * @return bool
1025
	 * @see elgg_set_ignore_access()
1026
	 */
1027 338
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1028 338
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1029
	}
1030
1031
	/**
1032
	 * Can a user add an entity to this container
1033
	 *
1034
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1035
	 * @param string $type      The type of entity we're looking to write
1036
	 * @param string $subtype   The subtype of the entity we're looking to write
1037
	 *
1038
	 * @return bool
1039
	 * @see elgg_set_ignore_access()
1040
	 */
1041 407
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1042 407
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1043
	}
1044
1045
	/**
1046
	 * Can a user comment on an entity?
1047
	 *
1048
	 * @tip Can be overridden by registering for the permissions_check:comment,
1049
	 * <entity type> plugin hook.
1050
	 *
1051
	 * @param int  $user_guid User guid (default is logged in user)
1052
	 * @param bool $default   Default permission
1053
	 * @return bool
1054
	 */
1055 4
	public function canComment($user_guid = 0, $default = null) {
1056 4
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1057
	}
1058
1059
	/**
1060
	 * Can a user annotate an entity?
1061
	 *
1062
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1063
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1064
	 *
1065
	 * @tip If you want logged out users to annotate an object, do not call
1066
	 * canAnnotate(). It's easier than using the plugin hook.
1067
	 *
1068
	 * @param int    $user_guid       User guid (default is logged in user)
1069
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1070
	 *
1071
	 * @return bool
1072
	 */
1073 8
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1074 8
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1075
	}
1076
1077
	/**
1078
	 * Returns the access_id.
1079
	 *
1080
	 * @return int The access ID
1081
	 */
1082
	public function getAccessID() {
1083
		return $this->access_id;
1084
	}
1085
1086
	/**
1087
	 * Returns the guid.
1088
	 *
1089
	 * @return int|null GUID
1090
	 */
1091 548
	public function getGUID() {
1092 548
		return $this->guid;
1093
	}
1094
1095
	/**
1096
	 * Returns the entity type
1097
	 *
1098
	 * @return string The entity type
1099
	 */
1100 1
	public function getType() {
1101
		// this is just for the PHPUnit mocking framework
1102 1
		return $this->type;
1103
	}
1104
1105
	/**
1106
	 * Get the entity subtype
1107
	 *
1108
	 * @return string The entity subtype
1109
	 */
1110 566
	public function getSubtype() {
1111 566
		return $this->attributes['subtype'];
1112
	}
1113
1114
	/**
1115
	 * Get the guid of the entity's owner.
1116
	 *
1117
	 * @return int The owner GUID
1118
	 */
1119 95
	public function getOwnerGUID() {
1120 95
		return (int) $this->owner_guid;
1121
	}
1122
1123
	/**
1124
	 * Gets the \ElggEntity that owns this entity.
1125
	 *
1126
	 * @return \ElggEntity The owning entity