Passed
Push — master ( c2d8e3...289151 )
by Jeroen
06:06
created

ElggEntity::getMetadata()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 17
nc 6
nop 1
dl 0
loc 29
ccs 14
cts 14
cp 1
crap 6
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
use Elgg\EntityIcon;
4
5
/**
6
 * The parent class for all Elgg Entities.
7
 *
8
 * An \ElggEntity is one of the basic data models in Elgg.
9
 * It is the primary means of storing and retrieving data from the database.
10
 * An \ElggEntity represents one row of the entities table.
11
 *
12
 * The \ElggEntity class handles CRUD operations for the entities table.
13
 * \ElggEntity should always be extended by another class to handle CRUD
14
 * operations on the type-specific table.
15
 *
16
 * \ElggEntity uses magic methods for get and set, so any property that isn't
17
 * declared will be assumed to be metadata and written to the database
18
 * as metadata on the object.  All children classes must declare which
19
 * properties are columns of the type table or they will be assumed
20
 * to be metadata.  See \ElggObject::initializeAttributes() for examples.
21
 *
22
 * Core supports 4 types of entities: \ElggObject, \ElggUser, \ElggGroup, and \ElggSite.
23
 *
24
 * @tip Plugin authors will want to extend the \ElggObject class, not this class.
25
 *
26
 * @package    Elgg.Core
27
 * @subpackage DataModel.Entities
28
 *
29
 * @property       string $type           object, user, group, or site (read-only after save)
30
 * @property       string $subtype        Further clarifies the nature of the entity
31
 * @property-read  int    $guid           The unique identifier for this entity (read only)
32
 * @property       int    $owner_guid     The GUID of the owner of this entity (usually the creator)
33
 * @property       int    $container_guid The GUID of the entity containing this entity
34
 * @property       int    $access_id      Specifies the visibility level of this entity
35
 * @property       int    $time_created   A UNIX timestamp of when the entity was created
36
 * @property-read  int    $time_updated   A UNIX timestamp of when the entity was last updated (automatically updated on save)
37
 * @property-read  int    $last_action    A UNIX timestamp of when the entity was last acted upon
38
 * @property       string $enabled        Is this entity enabled ('yes' or 'no')
39
 *
40
 * Metadata (the above are attributes)
41
 * @property       string $location       A location of the entity
42
 */
43
abstract class ElggEntity extends \ElggData implements
44
	Locatable, // Geocoding interface
45
	EntityIcon // Icon interface
46
{
47
48
	public static $primary_attr_names = [
49
		'guid',
50
		'type',
51
		'subtype',
52
		'owner_guid',
53
		'container_guid',
54
		'access_id',
55
		'time_created',
56
		'time_updated',
57
		'last_action',
58
		'enabled',
59
	];
60
61
	protected static $integer_attr_names = [
62
		'guid',
63
		'owner_guid',
64
		'container_guid',
65
		'access_id',
66
		'time_created',
67
		'time_updated',
68
		'last_action',
69
	];
70
71
	/**
72
	 * Holds metadata until entity is saved.  Once the entity is saved,
73
	 * metadata are written immediately to the database.
74
	 * @var array
75
	 */
76
	protected $temp_metadata = [];
77
78
	/**
79
	 * Holds annotations until entity is saved.  Once the entity is saved,
80
	 * annotations are written immediately to the database.
81
	 * @var array
82
	 */
83
	protected $temp_annotations = [];
84
85
	/**
86
	 * Holds private settings until entity is saved. Once the entity is saved,
87
	 * private settings are written immediately to the database.
88
	 * @var array
89
	 */
90
	protected $temp_private_settings = [];
91
92
	/**
93
	 * Volatile data structure for this object, allows for storage of data
94
	 * in-memory that isn't sync'd back to the metadata table.
95
	 * @var array
96
	 */
97
	protected $volatile = [];
98
99
	/**
100
	 * Holds the original (persisted) attribute values that have been changed but not yet saved.
101
	 * @var array
102
	 */
103
	protected $orig_attributes = [];
104
105
	/**
106
	 * @var bool
107
	 */
108
	protected $_is_cacheable = true;
109
110
	/**
111
	 * Create a new entity.
112
	 *
113
	 * Plugin developers should only use the constructor to create a new entity.
114
	 * To retrieve entities, use get_entity() and the elgg_get_entities* functions.
115
	 *
116
	 * If no arguments are passed, it creates a new entity.
117
	 * If a database result is passed as a \stdClass instance, it instantiates
118
	 * that entity.
119
	 *
120
	 * @param stdClass $row Database row result. Default is null to create a new object.
121
	 *
122
	 * @throws IOException If cannot load remaining data from db
123
	 */
124 5322
	public function __construct(stdClass $row = null) {
125 5322
		$this->initializeAttributes();
126
127 5322
		if ($row && !$this->load($row)) {
128
			$msg = "Failed to load new " . get_class() . " for GUID:" . $row->guid;
129
			throw new \IOException($msg);
130
		}
131 5322
	}
132
133
	/**
134
	 * Initialize the attributes array.
135
	 *
136
	 * This is vital to distinguish between metadata and base parameters.
137
	 *
138
	 * @return void
139
	 */
140 5322
	protected function initializeAttributes() {
141 5322
		parent::initializeAttributes();
142
143 5322
		$this->attributes['guid'] = null;
144 5322
		$this->attributes['type'] = null;
145 5322
		$this->attributes['subtype'] = null;
146
147 5322
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
148 5322
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
149
150 5322
		$this->attributes['access_id'] = ACCESS_PRIVATE;
151 5322
		$this->attributes['time_updated'] = null;
152 5322
		$this->attributes['last_action'] = null;
153 5322
		$this->attributes['enabled'] = "yes";
154
155 5322
		$this->attributes['type'] = $this->getType();
156 5322
	}
157
158
	/**
159
	 * Clone an entity
160
	 *
161
	 * Resets the guid so that the entity can be saved as a distinct entity from
162
	 * the original. Creation time will be set when this new entity is saved.
163
	 * The owner and container guids come from the original entity. The clone
164
	 * method copies metadata but does not copy annotations or private settings.
165
	 *
166
	 * @note metadata will have its owner and access id set when the entity is saved
167
	 * and it will be the same as that of the entity.
168
	 *
169
	 * @return void
170
	 */
171 1
	public function __clone() {
172 1
		$orig_entity = get_entity($this->guid);
173 1
		if (!$orig_entity) {
174
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
175
			return;
176
		}
177
178 1
		$metadata_array = elgg_get_metadata([
179 1
			'guid' => $this->guid,
180 1
			'limit' => 0
181
		]);
182
183 1
		$this->attributes['guid'] = null;
184 1
		$this->attributes['time_created'] = null;
185 1
		$this->attributes['time_updated'] = null;
186 1
		$this->attributes['last_action'] = null;
187
188 1
		$this->attributes['subtype'] = $orig_entity->getSubtype();
189
190
		// copy metadata over to new entity - slightly convoluted due to
191
		// handling of metadata arrays
192 1
		if (is_array($metadata_array)) {
193
			// create list of metadata names
194 1
			$metadata_names = [];
195 1
			foreach ($metadata_array as $metadata) {
196 1
				$metadata_names[] = $metadata['name'];
197
			}
198
			// arrays are stored with multiple enties per name
199 1
			$metadata_names = array_unique($metadata_names);
200
201
			// move the metadata over
202 1
			foreach ($metadata_names as $name) {
203 1
				$this->__set($name, $orig_entity->$name);
204
			}
205
		}
206 1
	}
207
208
	/**
209
	 * Set an attribute or metadata value for this entity
210
	 *
211
	 * Anything that is not an attribute is saved as metadata.
212
	 *
213
	 * @warning Metadata set this way will inherit the entity's owner and
214
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
215
	 *
216
	 * @param string $name  Name of the attribute or metadata
217
	 * @param mixed  $value The value to be set
218
	 * @return void
219
	 * @see \ElggEntity::setMetadata()
220
	 */
221 1092
	public function __set($name, $value) {
222 1092
		if ($this->$name === $value) {
223
			// quick return if value is not changing
224 289
			return;
225
		}
226
227
		// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
228
		// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
229
		// the value to empty string, don't consider this a change.
230 1092
		if (in_array($name, ['title', 'name', 'description'])
231 1092
			&& $this->$name === null
232 1092
			&& $value === "") {
233
			return;
234
		}
235
236 1092
		if (array_key_exists($name, $this->attributes)) {
237
			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
238 589
			if (is_int($this->attributes[$name])
239 589
					&& is_string($value)
240 589
					&& ((string) $this->attributes[$name] === $value)) {
241 1
				return;
242
			}
243
244
			// keep original values
245 589
			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
246 9
				$this->orig_attributes[$name] = $this->attributes[$name];
247
			}
248
249
			// Certain properties should not be manually changed!
250 589
			switch ($name) {
251
				case 'guid':
252
				case 'time_updated':
253
				case 'last_action':
254
					return;
255
					break;
0 ignored issues
show
Unused Code introduced by Brett Profitt
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
256
				case 'access_id':
257
				case 'owner_guid':
258
				case 'container_guid':
259 482
					if ($value !== null) {
260 482
						$this->attributes[$name] = (int) $value;
261
					} else {
262
						$this->attributes[$name] = null;
263
					}
264 482
					break;
265
				default:
266 443
					$this->attributes[$name] = $value;
267 443
					break;
268
			}
269 589
			return;
270
		}
271
272 1062
		$this->setMetadata($name, $value);
273 1062
	}
274
275
	/**
276
	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
277
	 *
278
	 * @return array
279
	 */
280 59
	public function getOriginalAttributes() {
281 59
		return $this->orig_attributes;
282
	}
283
284
	/**
285
	 * Get an attribute or metadata value
286
	 *
287
	 * If the name matches an attribute, the attribute is returned. If metadata
288
	 * does not exist with that name, a null is returned.
289
	 *
290
	 * This only returns an array if there are multiple values for a particular
291
	 * $name key.
292
	 *
293
	 * @param string $name Name of the attribute or metadata
294
	 * @return mixed
295
	 */
296 5387
	public function __get($name) {
297 5387
		if (array_key_exists($name, $this->attributes)) {
298 5386
			return $this->attributes[$name];
299
		}
300
301 5363
		return $this->getMetadata($name);
302
	}
303
304
	/**
305
	 * Get the entity's display name
306
	 *
307
	 * @return string The title or name of this entity.
308
	 */
309 19
	public function getDisplayName() {
310 19
		return $this->name;
311
	}
312
313
	/**
314
	 * Sets the title or name of this entity.
315
	 *
316
	 * @param string $display_name The title or name of this entity.
317
	 * @return void
318
	 */
319
	public function setDisplayName($display_name) {
320
		$this->name = $display_name;
1 ignored issue
show
Bug Best Practice introduced by Jeroen Dalsem
The property name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
321
	}
322
323
	/**
324
	 * Return the value of a piece of metadata.
325
	 *
326
	 * @param string $name Name
327
	 *
328
	 * @return mixed The value, or null if not found.
329
	 */
330 5363
	public function getMetadata($name) {
331 5363
		$guid = $this->guid;
332
333 5363
		if (!$guid) {
334 572
			if (isset($this->temp_metadata[$name])) {
335
				// md is returned as an array only if more than 1 entry
336 285
				if (count($this->temp_metadata[$name]) == 1) {
337 285
					return $this->temp_metadata[$name][0];
338
				} else {
339 158
					return $this->temp_metadata[$name];
340
				}
341
			} else {
342 572
				return null;
343
			}
344
		}
345
346
		// upon first cache miss, just load/cache all the metadata and retry.
347
		// if this works, the rest of this function may not be needed!
348 5354
		$cache = _elgg_services()->metadataCache;
349 5354
		if ($cache->isLoaded($guid)) {
350 2238
			return $cache->getSingle($guid, $name);
351
		} else {
352 4136
			$cache->populateFromEntities([$guid]);
353
			// in case ignore_access was on, we have to check again...
354 4136
			if ($cache->isLoaded($guid)) {
355 1082
				return $cache->getSingle($guid, $name);
356
			}
357
358
			return null;
359 4083
		}
360 4083
	}
361 4083
362 4083
	/**
363
	 * Unset a property from metadata or attribute.
364
	 *
365
	 * @warning If you use this to unset an attribute, you must save the object!
366 4083
	 *
367
	 * @param string $name The name of the attribute or metadata.
368 4083
	 *
369
	 * @return void
370 4083
	 * @todo some attributes should be set to null or other default values
371
	 */
372 4083
	public function __unset($name) {
373
		if (array_key_exists($name, $this->attributes)) {
374
			$this->attributes[$name] = "";
375
		} else {
376 4083
			$this->deleteMetadata($name);
377
		}
378
	}
379
380
	/**
381
	 * Set metadata on this entity.
382
	 *
383
	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
384
	 * Use this method if you want to explicitly set the owner or access of the metadata.
385
	 * You cannot set the owner/access before the entity has been saved.
386
	 *
387
	 * @param string $name       Name of the metadata
388
	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
389 49
	 * @param string $value_type 'text', 'integer', or '' for automatic detection
390 49
	 * @param bool   $multiple   Allow multiple values for a single name.
391
	 *                           Does not support associative arrays.
392
	 *
393 49
	 * @return bool
394
	 * @throws InvalidArgumentException
395 49
	 */
396
	public function setMetadata($name, $value, $value_type = '', $multiple = false) {
397
398
		// normalize value to an array that we will loop over
399
		// remove indexes if value already an array.
400
		if (is_array($value)) {
401
			$value = array_values($value);
402
		} else {
403
			$value = [$value];
404
		}
405
406
		// strip null values from array
407
		$value = array_filter($value, function($var) {
408
			return !is_null($var);
409
		});
410
411
		if (empty($this->guid)) {
412
			// unsaved entity. store in temp array
413 1062
			return $this->setTempMetadata($name, $value, $multiple);
414
		}
415
416
		// saved entity. persist md to db.
417 1062
		if (!$multiple) {
418 162
			$current_metadata = $this->getMetadata($name);
419
420 1061
			if ((is_array($current_metadata) || count($value) > 1 || $value === []) && isset($current_metadata)) {
421
				// remove current metadata if needed
422
				// need to remove access restrictions right now to delete
423
				// because this is the expected behavior
424 1062
				$delete_result = elgg_call(ELGG_IGNORE_ACCESS, function() use ($name) {
425 1062
					return elgg_delete_metadata([
426 1062
						'guid' => $this->guid,
427
						'metadata_name' => $name,
428 1062
						'limit' => false,
429
					]);
430 569
				});
431
432
				if (false === $delete_result) {
433
					return false;
434 1028
				}
435 1028
			}
436
437 1028
			if (count($value) > 1) {
438
				// new value is a multiple valued metadata
439
				$multiple = true;
440
			}
441 8
		}
442 8
443 8
		// create new metadata
444 8
		foreach ($value as $value_tmp) {
445
			$metadata = new ElggMetadata();
446
			$metadata->entity_guid = $this->guid;
447 8
			$metadata->name = $name;
448
			$metadata->value_type = $value_type;
449 8
			$metadata->value = $value_tmp;
450
			$md_id = _elgg_services()->metadataTable->create($metadata, $multiple);
451
			if (!$md_id) {
452
				return false;
453
			}
454 1028
		}
455
456 162
		return true;
457
	}
458
459
	/**
460
	 * Set temp metadata on this entity.
461 1028
	 *
462 1028
	 * @param string $name     Name of the metadata
463 1028
	 * @param mixed  $value    Value of the metadata (doesn't support assoc arrays)
464 1028
	 * @param bool   $multiple Allow multiple values for a single name.
465 1028
	 *                         Does not support associative arrays.
466 1028
	 *
467 1028
	 * @return bool
468 1028
	 */
469 1028
	protected function setTempMetadata($name, $value, $multiple = false) {
470
		// if overwrite, delete first
471
		if (!$multiple) {
472
			unset($this->temp_metadata[$name]);
473 1028
			if (count($value)) {
474
				// only save if value array contains data
475
				$this->temp_metadata[$name] = $value;
476
			}
477
			return true;
478
		}
479
480
		if (!isset($this->temp_metadata[$name])) {
481
			$this->temp_metadata[$name] = [];
482
		}
483
484
		$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
485
486 569
		return true;
487
	}
488 569
489 569
490 569
491
	/**
492 569
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
493
	 * If you pass a name, only metadata matching that name will be deleted.
494 569
	 *
495
	 * @warning Calling this with no $name will clear all metadata on the entity.
496
	 *
497 3
	 * @param null|string $name The name of the metadata to remove.
498
	 * @return bool|null
499
	 * @since 1.8
500
	 */
501 3
	public function deleteMetadata($name = null) {
502
503 3
		if (!$this->guid) {
504
			// remove from temp_metadata
505
			if ($name) {
0 ignored issues
show
Bug Best Practice introduced by Jeroen Dalsem
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
506
				if (!isset($this->temp_metadata[$name])) {
507
					return null;
508
				} else {
509
					unset($this->temp_metadata[$name]);
510
					return true;
511
				}
512
			} else {
513
				$this->temp_metadata = [];
514
				return true;
515
			}
516
		}
517
518 266
		$options = [
519
			'guid' => $this->guid,
520 266
			'limit' => 0
521
		];
522 2
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
523 2
			$options['metadata_name'] = $name;
524
		}
525
526 2
		return elgg_delete_metadata($options);
527 2
	}
528
529
	/**
530
	 * Get a piece of volatile (non-persisted) data on this entity.
531
	 *
532
	 * @param string $name The name of the volatile data
533
	 *
534
	 * @return mixed The value or null if not found.
535
	 */
536 266
	public function getVolatileData($name) {
537 266
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
538
	}
539 266
540 55
	/**
541
	 * Set a piece of volatile (non-persisted) data on this entity
542
	 *
543 266
	 * @param string $name  Name
544
	 * @param mixed  $value Value
545
	 *
546
	 * @return void
547
	 */
548
	public function setVolatileData($name, $value) {
549
		$this->volatile[$name] = $value;
550
	}
551
552
	/**
553 25
	 * Remove all relationships to and from this entity.
554 25
	 * If you pass a relationship name, only relationships matching that name
555
	 * will be deleted.
556
	 *
557
	 * @warning Calling this with no $relationship will clear all relationships
558
	 * for this entity.
559
	 *
560
	 * @param null|string $relationship The name of the relationship to remove.
561
	 * @return bool
562
	 * @see \ElggEntity::addRelationship()
563
	 * @see \ElggEntity::removeRelationship()
564
	 */
565 26
	public function deleteRelationships($relationship = null) {
566 26
		$relationship = (string) $relationship;
567 26
		$result = remove_entity_relationships($this->getGUID(), $relationship);
568
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
569
	}
570
571
	/**
572
	 * Add a relationship between this an another entity.
573
	 *
574
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
575
	 *
576
	 * @param int    $guid_two     GUID of the target entity of the relationship.
577
	 * @param string $relationship The type of relationship.
578
	 *
579
	 * @return bool
580
	 * @see \ElggEntity::removeRelationship()
581
	 * @see \ElggEntity::deleteRelationships()
582 214
	 */
583 214
	public function addRelationship($guid_two, $relationship) {
584 214
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
585 214
	}
586
587
	/**
588
	 * Remove a relationship
589
	 *
590
	 * @param int    $guid_two     GUID of the target entity of the relationship.
591
	 * @param string $relationship The type of relationship.
592
	 *
593
	 * @return bool
594
	 * @see \ElggEntity::addRelationship()
595
	 * @see \ElggEntity::deleteRelationships()
596
	 */
597
	public function removeRelationship($guid_two, $relationship) {
598
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
599
	}
600 5
601 5
	/**
602
	 * Adds a private setting to this entity.
603
	 *
604
	 * @warning Private settings are stored as strings
605
	 *          Unlike metadata and annotations there is no reverse casting when you retrieve the setting
606
	 *          When saving integers, they will be cast to strings
607
	 *          Booleans will be cast to '0' and '1'
608
	 *
609
	 * @param string $name  Name of private setting
610
	 * @param mixed  $value Value of private setting
611
	 *
612
	 * @return bool
613
	 * @throws DatabaseException
614 1
	 */
615 1
	public function setPrivateSetting($name, $value) {
616
		if (is_bool($value)) {
617
			$value = (int) $value;
618
		}
619
620
		if (!$this->guid) {
621
			$this->temp_private_settings[$name] = $value;
622
623
			return true;
624
		}
625
626
		return _elgg_services()->privateSettings->set($this, $name, $value);
627
	}
628
629 124
	/**
630 124
	 * Returns a private setting value
631 123
	 *
632
	 * @warning Private settings are always returned as strings
633 45
	 *          Make sure you can your values back to expected type
634 45
	 *
635
	 * @param string $name Name of the private setting
636
	 *
637
	 * @return string|null
638
	 * @throws DatabaseException
639
	 */
640
	public function getPrivateSetting($name) {
641
		if (!$this->guid) {
642
			return elgg_extract($name, $this->temp_private_settings);
643
		}
644
645 37
		return _elgg_services()->privateSettings->get($this, $name);
646 37
	}
647 37
648
	/**
649 5
	 * Returns all private settings
650 5
	 *
651
	 * @return array
652
	 * @throws DatabaseException
653 5
	 */
654
	public function getAllPrivateSettings() {
655
		if (!$this->guid) {
656
			return $this->temp_private_settings;
657
		}
658
659
		return _elgg_services()->privateSettings->getAllForEntity($this);
660
	}
661
662
	/**
663 3
	 * Removes private setting
664 3
	 *
665
	 * @param string $name Name of the private setting
666
	 *
667
	 * @return bool
668
	 * @throws DatabaseException
669
	 */
670
	public function removePrivateSetting($name) {
671
		if (!$this->guid) {
672
			unset($this->temp_private_settings[$name]);
673
			return true;
674
		}
675
676
		return _elgg_services()->privateSettings->remove($this, $name);
677 214
	}
678
679 214
	/**
680 214
	 * Removes all private settings
681
	 *
682 214
	 * @return bool
683 1
	 * @throws DatabaseException
684
	 */
685
	public function removeAllPrivateSettings() {
686 214
		if (!$this->guid) {
687
			$this->temp_private_settings = [];
688
			return true;
689
		}
690
691
		return _elgg_services()->privateSettings->removeAllForEntity($this);
692
	}
693
694
	/**
695
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
696
	 * If you pass a name, only annotations matching that name will be deleted.
697 214
	 *
698
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
699
	 *
700 214
	 * @param null|string $name The annotations name to remove.
701
	 * @return bool
702 214
	 * @since 1.8
703 214
	 */
704
	public function deleteAnnotations($name = null) {
705 214
		$options = [
706
			'guid' => $this->guid,
707
			'limit' => 0
708
		];
709 214
		if ($name) {
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
710 214
			$options['annotation_name'] = $name;
711 214
		}
712
713
		return elgg_delete_annotations($options);
714
	}
715
716
	/**
717
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
718
	 * If you pass a name, only annotations matching that name will be deleted.
719
	 *
720
	 * @param null|string $name The name of annotations to delete.
721 5
	 * @return bool
722
	 * @since 1.8
723 5
	 */
724 5
	public function deleteOwnedAnnotations($name = null) {
725
		// access is turned off for this because they might
726 5
		// no longer have access to an entity they created annotations on
727
728
		$flags = ELGG_IGNORE_ACCESS;
729
		$callback = function() use ($name) {
730 5
			return elgg_delete_annotations([
731
				'annotation_owner_guid' => $this->guid,
732
				'limit' => 0,
733
				'annotation_name' => $name,
734
			]);
735
		};
736
737
		return elgg_call($flags, $callback);
738
	}
739
740
	/**
741
	 * Disables annotations for this entity, optionally based on name.
742 3
	 *
743
	 * @param string $name An options name of annotations to disable.
744 3
	 * @return bool
745 3
	 * @since 1.8
746
	 */
747 3
	public function disableAnnotations($name = '') {
748
		$options = [
749
			'guid' => $this->guid,
750
			'limit' => 0
751 3
		];
752
		if ($name) {
753
			$options['annotation_name'] = $name;
754
		}
755
756
		return elgg_disable_annotations($options);
757
	}
758
759
	/**
760
	 * Enables annotations for this entity, optionally based on name.
761 2
	 *
762
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
763 2
	 *
764
	 * @param string $name An options name of annotations to enable.
765 2
	 * @return bool
766 2
	 * @since 1.8
767
	 */
768
	public function enableAnnotations($name = '') {
769 2
		$options = [
770
			'guid' => $this->guid,
771
			'limit' => 0
772
		];
773
		if ($name) {
774
			$options['annotation_name'] = $name;
775
		}
776
777
		return elgg_enable_annotations($options);
778
	}
779
780
	/**
781
	 * Helper function to return annotation calculation results
782
	 *
783
	 * @param string $name        The annotation name.
784
	 * @param string $calculation A valid MySQL function to run its values through
785
	 * @return mixed
786
	 */
787
	private function getAnnotationCalculation($name, $calculation) {
788
		$options = [
789
			'guid' => $this->getGUID(),
790
			'distinct' => false,
791 93
			'annotation_name' => $name,
792 93
			'annotation_calculation' => $calculation
793 93
		];
794 92
795
		return elgg_get_annotations($options);
796 93
	}
797 93
798 93
	/**
799 93
	 * Adds an annotation to an entity.
800 93
	 *
801 93
	 * @warning By default, annotations are private.
802 93
	 *
803 93
	 * @warning Annotating an unsaved entity more than once with the same name
804
	 *          will only save the last annotation.
805 17
	 *
806
	 * @todo Update temp_annotations to store an instance of ElggAnnotation and simply call ElggAnnotation::save(),
807 17
	 *       after entity is saved
808
	 *
809
	 * @param string $name       Annotation name
810
	 * @param mixed  $value      Annotation value
811
	 * @param int    $access_id  Access ID
812
	 * @param int    $owner_guid GUID of the annotation owner
813
	 * @param string $value_type The type of annotation value
814
	 *
815
	 * @return bool|int Returns int if an annotation is saved
816
	 */
817
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $value_type = "") {
818
		if ($this->guid) {
819
			if (!$owner_guid) {
820
				$owner_guid = elgg_get_logged_in_user_guid();
821 9
			}
822 9
			$annotation = new ElggAnnotation();
823 9
			$annotation->entity_guid = $this->guid;
824
			$annotation->name = $name;
825 9
			$annotation->value_type = $value_type;
826
			$annotation->value = $value;
827
			$annotation->owner_guid = $owner_guid;
828
			$annotation->access_id = $access_id;
829
			return $annotation->save();
830
		} else {
831
			$this->temp_annotations[$name] = $value;
832
		}
833
		return true;
834
	}
835
836
	/**
837
	 * Gets an array of annotations.
838
	 *
839
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
840
	 * as the options array.
841
	 *
842
	 * @param array $options Array of options for elgg_get_annotations() except guid.
843
	 *
844 2
	 * @return array
845 2
	 * @see elgg_get_annotations()
846
	 */
847
	public function getAnnotations(array $options = []) {
848
		if ($this->guid) {
849
			$options['guid'] = $this->guid;
850
851
			return elgg_get_annotations($options);
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return elgg_get_annotations($options) also could return the type integer|false which is incompatible with the documented return type array.
Loading history...
852
		} else {
853
			$name = elgg_extract('annotation_name', $options, '');
854
855
			if (isset($this->temp_annotations[$name])) {
856
				return [$this->temp_annotations[$name]];
857
			}
858
		}
859
860
		return [];
861
	}
862
863
	/**
864
	 * Count annotations.
865
	 *
866
	 * @param string $name The type of annotation.
867
	 *
868
	 * @return int
869
	 */
870
	public function countAnnotations($name = "") {
871
		return $this->getAnnotationCalculation($name, 'count');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->getAnnotat...ulation($name, 'count') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
872
	}
873
874
	/**
875
	 * Get the average of an integer type annotation.
876
	 *
877
	 * @param string $name Annotation name
878
	 *
879
	 * @return int
880
	 */
881
	public function getAnnotationsAvg($name) {
882
		return $this->getAnnotationCalculation($name, 'avg');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->getAnnotat...lculation($name, 'avg') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
883
	}
884
885
	/**
886
	 * Get the sum of integer type annotations of a given name.
887
	 *
888
	 * @param string $name Annotation name
889
	 *
890
	 * @return int
891
	 */
892
	public function getAnnotationsSum($name) {
893
		return $this->getAnnotationCalculation($name, 'sum');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->getAnnotat...lculation($name, 'sum') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
894
	}
895
896
	/**
897
	 * Get the minimum of integer type annotations of given name.
898 2
	 *
899 2
	 * @param string $name Annotation name
900 2
	 *
901
	 * @return int
902 2
	 */
903
	public function getAnnotationsMin($name) {
904
		return $this->getAnnotationCalculation($name, 'min');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->getAnnotat...lculation($name, 'min') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
905
	}
906 2
907 2
	/**
908 2
	 * Get the maximum of integer type annotations of a given name.
909 2
	 *
910
	 * @param string $name Annotation name
911
	 *
912
	 * @return int
913
	 */
914
	public function getAnnotationsMax($name) {
915
		return $this->getAnnotationCalculation($name, 'max');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->getAnnotat...lculation($name, 'max') also could return the type false|ElggAnnotation[] which is incompatible with the documented return type integer.
Loading history...
916
	}
917
918
	/**
919
	 * Count the number of comments attached to this entity.
920
	 *
921
	 * @return int Number of comments
922
	 * @since 1.8.0
923
	 */
924
	public function countComments() {
925 218
		$params = ['entity' => $this];
926 218
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
0 ignored issues
show
Bug introduced by Evan Winslow
Are you sure the assignment to $num is correct as _elgg_services()->hooks-...is->getType(), $params) targeting Elgg\PluginHooksService::trigger() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
927 218
928
		if (is_int($num)) {
929
			return $num;
930
		}
931
932
		return elgg_get_entities([
1 ignored issue
show
Bug Best Practice introduced by Jeroen Dalsem
The expression return elgg_get_entities..., 'distinct' => false)) also could return the type ElggEntity[]|false which is incompatible with the documented return type integer.
Loading history...
933
			'type' => 'object',
934
			'subtype' => 'comment',
935
			'container_guid' => $this->getGUID(),
936
			'count' => true,
937
			'distinct' => false,
938
		]);
939
	}
940
941
	/**
942
	 * Returns the ACLs owned by the entity
943
	 *
944
	 * @param array $options additional options to get the access collections with
945
	 *
946
	 * @return \ElggAccessCollection[]
947
	 *
948
	 * @see elgg_get_access_collections()
949
	 * @since 3.0
950
	 */
951
	public function getOwnedAccessCollections($options = []) {
952
		$options['owner_guid'] = $this->guid;
953
		return _elgg_services()->accessCollections->getEntityCollections($options);
954
	}
955
	
956
	/**
957
	 * Returns the first ACL owned by the entity with a given subtype
958
	 *
959
	 * @param string $subtype subtype of the ACL
960
	 *
961
	 * @return \ElggAccessCollection|false
962
	 *
963
	 * @since 3.0
964
	 */
965
	public function getOwnedAccessCollection($subtype) {
966
		if (!is_string($subtype) || $subtype === '') {
967
			return false;
968
		}
969
		
970
		$acls = $this->getOwnedAccessCollections([
971
			'subtype' => $subtype,
972
		]);
973
		
974
		return elgg_extract(0, $acls, false);
975
	}
976
977
	/**
978
	 * Gets an array of entities with a relationship to this entity.
979
	 *
980
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
981
	 *                       for a list of options. 'relationship_guid' is set to
982
	 *                       this entity.
983
	 *
984
	 * @return array|false An array of entities or false on failure
985
	 * @see elgg_get_entities_from_relationship()
986
	 */
987
	public function getEntitiesFromRelationship(array $options = []) {
988
		$options['relationship_guid'] = $this->guid;
989
		return elgg_get_entities($options);
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return elgg_get_entities($options) also could return the type integer which is incompatible with the documented return type false|array.
Loading history...
990
	}
991
992
	/**
993 399
	 * Gets the number of entities from a specific relationship type
994 399
	 *
995
	 * @param string $relationship         Relationship type (eg "friends")
996
	 * @param bool   $inverse_relationship Invert relationship
997
	 *
998
	 * @return int|false The number of entities or false on failure
999
	 */
1000
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
1001
		return elgg_get_entities([
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return elgg_get_entities...ship, 'count' => true)) also could return the type ElggEntity[] which is incompatible with the documented return type integer|false.
Loading history...
1002
			'relationship' => $relationship,
1003
			'relationship_guid' => $this->getGUID(),
1004
			'inverse_relationship' => $inverse_relationship,
1005
			'count' => true
1006
		]);
1007
	}
1008 462
1009 462
	/**
1010
	 * Can a user edit this entity?
1011
	 *
1012
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
1013
	 *
1014
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1015
	 *
1016
	 * @return bool Whether this entity is editable by the given user.
1017
	 * @see elgg_set_ignore_access()
1018
	 */
1019
	public function canEdit($user_guid = 0) {
1020
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
1021
	}
1022
1023
	/**
1024
	 * Can a user delete this entity?
1025
	 *
1026
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
1027 338
	 *
1028 338
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
1029
	 *
1030
	 * @return bool Whether this entity is deletable by the given user.
1031
	 * @since 1.11
1032
	 * @see elgg_set_ignore_access()
1033
	 */
1034
	public function canDelete($user_guid = 0) {
1035
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
1036
	}
1037
1038
	/**
1039
	 * Can a user edit metadata on this entity?
1040
	 *
1041 407
	 * If no specific metadata is passed, it returns whether the user can
1042 407
	 * edit any metadata on the entity.
1043
	 *
1044
	 * @tip Can be overridden by by registering for the permissions_check:metadata
1045
	 * plugin hook.
1046
	 *
1047
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
1048
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
1049
	 *
1050
	 * @return bool
1051
	 * @see elgg_set_ignore_access()
1052
	 */
1053
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1054
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1055 4
	}
1056 4
1057
	/**
1058
	 * Can a user add an entity to this container
1059
	 *
1060
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1061
	 * @param string $type      The type of entity we're looking to write
1062
	 * @param string $subtype   The subtype of the entity we're looking to write
1063
	 *
1064
	 * @return bool
1065
	 * @see elgg_set_ignore_access()
1066
	 */
1067
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1068
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1069
	}
1070