Passed
Push — 3.x ( 589a0f...691809 )
by Jerome
65:54 queued 11s
created

ElggEntity::removeAllRelatedRiverItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2101
			_elgg_services()->dataCache->entities->save($this->guid, /** @scrutinizer ignore-type */ $this);
Loading history...
2102
2103 1178
			$this->volatile = $tmp;
2104
		}
2105 1178
	}
2106
2107
	/**
2108
	 * Invalidate cache for entity
2109
	 *
2110
	 * @return void
2111
	 * @internal
2112
	 */
2113 1358
	public function invalidateCache() {
2114 1358
		if (!$this->guid) {
2115
			return;
2116
		}
2117
2118 1358
		_elgg_services()->entityCache->delete($this->guid);
2119
2120
		$namespaces = [
2121 1358
			'entities',
2122
			'metadata',
2123
			'private_settings',
2124
		];
2125
2126 1358
		foreach ($namespaces as $namespace) {
2127 1358
			_elgg_services()->dataCache->get($namespace)->delete($this->guid);
2128
		}
2129 1358
	}
2130
}
2131