Passed
Push — master ( de047a...00c46c )
by Jeroen
39:22 queued 09:12
created

ElggEntity::clearAllFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
425 3582
					return false;
426
				}
427
			}
428
429 3582
			return true;
430
		} else {
431
			// unsaved entity. store in temp array
432
			// returning single entries instead of an array of 1 element is decided in
433
			// getMetaData(), just like pulling from the db.
434
435 229
			if ($owner_guid != 0) {
436
				$msg = "owner guid cannot be used in ElggEntity::setMetadata() until entity is saved.";
437
				throw new \InvalidArgumentException($msg);
438
			}
439
440
			// if overwrite, delete first
441 229
			if (!$multiple) {
442 229
				$this->temp_metadata[$name] = $value;
443 229
				return true;
444
			}
445
446 3
			if (!isset($this->temp_metadata[$name])) {
447
				$this->temp_metadata[$name] = [];
448
			}
449
450
			// add new md
451 3
			$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
452 3
			return true;
453
		}
454
	}
455
456
	/**
457
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
458
	 * If you pass a name, only metadata matching that name will be deleted.
459
	 *
460
	 * @warning Calling this with no $name will clear all metadata on the entity.
461
	 *
462
	 * @param null|string $name The name of the metadata to remove.
463
	 * @return bool
464
	 * @since 1.8
465
	 */
466 233
	public function deleteMetadata($name = null) {
467
468 233
		if (!$this->guid) {
469
			return false;
470
		}
471
472
		$options = [
473 233
			'guid' => $this->guid,
474 233
			'limit' => 0
475
		];
476 233
		if ($name) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
477 47
			$options['metadata_name'] = $name;
478
		}
479
480 233
		return elgg_delete_metadata($options);
481
	}
482
483
	/**
484
	 * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
485
	 * If you pass a name, only metadata matching that name will be deleted.
486
	 *
487
	 * @param null|string $name The name of metadata to delete.
488
	 * @return bool
489
	 * @since 1.8
490
	 */
491 192 View Code Duplication
	public function deleteOwnedMetadata($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
492
		// access is turned off for this because they might
493
		// no longer have access to an entity they created metadata on.
494 192
		$ia = elgg_set_ignore_access(true);
495
		$options = [
496 192
			'metadata_owner_guid' => $this->guid,
497 192
			'limit' => 0
498
		];
499 192
		if ($name) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
500
			$options['metadata_name'] = $name;
501
		}
502
503 192
		$r = elgg_delete_metadata($options);
504 192
		elgg_set_ignore_access($ia);
505 192
		return $r;
506
	}
507
508
	/**
509
	 * Disables metadata for this entity, optionally based on name.
510
	 *
511
	 * @param string $name An options name of metadata to disable.
512
	 * @return bool
513
	 * @since 1.8
514
	 */
515 6 View Code Duplication
	public function disableMetadata($name = '') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
516
		$options = [
517 6
			'guid' => $this->guid,
518 6
			'limit' => 0
519
		];
520 6
		if ($name) {
521 1
			$options['metadata_name'] = $name;
522
		}
523
524 6
		return elgg_disable_metadata($options);
525
	}
526
527
	/**
528
	 * Enables metadata for this entity, optionally based on name.
529
	 *
530
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
531
	 *
532
	 * @param string $name An options name of metadata to enable.
533
	 * @return bool
534
	 * @since 1.8
535
	 */
536 4 View Code Duplication
	public function enableMetadata($name = '') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
537
		$options = [
538 4
			'guid' => $this->guid,
539 4
			'limit' => 0
540
		];
541 4
		if ($name) {
542 1
			$options['metadata_name'] = $name;
543
		}
544
545 4
		return elgg_enable_metadata($options);
546
	}
547
548
	/**
549
	 * Get a piece of volatile (non-persisted) data on this entity.
550
	 *
551
	 * @param string $name The name of the volatile data
552
	 *
553
	 * @return mixed The value or null if not found.
554
	 */
555 9
	public function getVolatileData($name) {
556 9
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
557
	}
558
559
	/**
560
	 * Set a piece of volatile (non-persisted) data on this entity
561
	 *
562
	 * @param string $name  Name
563
	 * @param mixed  $value Value
564
	 *
565
	 * @return void
566
	 */
567 18
	public function setVolatileData($name, $value) {
568 18
		$this->volatile[$name] = $value;
569 18
	}
570
571
	/**
572
	 * Cache the entity in a persisted cache
573
	 *
574
	 * @param ElggSharedMemoryCache $cache       Memcache or null cache
575
	 * @param int                   $last_action Last action time
576
	 *
577
	 * @return void
578
	 * @access private
579
	 * @internal
580
	 */
581 355
	public function storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action = 0) {
582 355
		$tmp = $this->volatile;
583
584
		// don't store volatile data
585 355
		$this->volatile = [];
586 355
		if ($last_action) {
587
			$this->last_action = (int) $last_action;
588
		}
589 355
		$cache->save($this->guid, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<ElggEntity>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
590
591 355
		$this->volatile = $tmp;
592 355
	}
593
594
	/**
595
	 * Remove all relationships to and from this entity.
596
	 * If you pass a relationship name, only relationships matching that name
597
	 * will be deleted.
598
	 *
599
	 * @warning Calling this with no $relationship will clear all relationships
600
	 * for this entity.
601
	 *
602
	 * @param null|string $relationship The name of the relationship to remove.
603
	 * @return bool
604
	 * @see \ElggEntity::addRelationship()
605
	 * @see \ElggEntity::removeRelationship()
606
	 */
607 192
	public function deleteRelationships($relationship = null) {
608 192
		$relationship = (string) $relationship;
609 192
		$result = remove_entity_relationships($this->getGUID(), $relationship);
610 192
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
611
	}
612
613
	/**
614
	 * Add a relationship between this an another entity.
615
	 *
616
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
617
	 *
618
	 * @param int    $guid_two     GUID of the target entity of the relationship.
619
	 * @param string $relationship The type of relationship.
620
	 *
621
	 * @return bool
622
	 * @see \ElggEntity::removeRelationship()
623
	 * @see \ElggEntity::deleteRelationships()
624
	 */
625 3
	public function addRelationship($guid_two, $relationship) {
626 3
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
627
	}
628
629
	/**
630
	 * Remove a relationship
631
	 *
632
	 * @param int    $guid_two     GUID of the target entity of the relationship.
633
	 * @param string $relationship The type of relationship.
634
	 *
635
	 * @return bool
636
	 * @see \ElggEntity::addRelationship()
637
	 * @see \ElggEntity::deleteRelationships()
638
	 */
639 1
	public function removeRelationship($guid_two, $relationship) {
640 1
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
641
	}
642
643
	/**
644
	 * Adds a private setting to this entity.
645
	 *
646
	 * Private settings are similar to metadata but will not
647
	 * be searched and there are fewer helper functions for them.
648
	 *
649
	 * @param string $name  Name of private setting
650
	 * @param mixed  $value Value of private setting
651
	 *
652
	 * @return bool
653
	 */
654 33
	public function setPrivateSetting($name, $value) {
655 33
		if ((int) $this->guid > 0) {
656 33
			return set_private_setting($this->getGUID(), $name, $value);
657
		} else {
658 29
			$this->temp_private_settings[$name] = $value;
659 29
			return true;
660
		}
661
	}
662
663
	/**
664
	 * Returns a private setting value
665
	 *
666
	 * @param string $name Name of the private setting
667
	 *
668
	 * @return mixed Null if the setting does not exist
669
	 */
670 9
	public function getPrivateSetting($name) {
671 9
		if ((int) ($this->guid) > 0) {
672 9
			return get_private_setting($this->getGUID(), $name);
673
		} else {
674 5
			if (isset($this->temp_private_settings[$name])) {
675 5
				return $this->temp_private_settings[$name];
676
			}
677
		}
678
		return null;
679
	}
680
681
	/**
682
	 * Removes private setting
683
	 *
684
	 * @param string $name Name of the private setting
685
	 *
686
	 * @return bool
687
	 */
688
	public function removePrivateSetting($name) {
689
		return remove_private_setting($this->getGUID(), $name);
690
	}
691
692
	/**
693
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
694
	 * If you pass a name, only annotations matching that name will be deleted.
695
	 *
696
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
697
	 *
698
	 * @param null|string $name The annotations name to remove.
699
	 * @return bool
700
	 * @since 1.8
701
	 */
702 192 View Code Duplication
	public function deleteAnnotations($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
703
		$options = [
704 192
			'guid' => $this->guid,
705 192
			'limit' => 0
706
		];
707 192
		if ($name) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
708 1
			$options['annotation_name'] = $name;
709
		}
710
711 192
		return elgg_delete_annotations($options);
712
	}
713
714
	/**
715
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
716
	 * If you pass a name, only annotations matching that name will be deleted.
717
	 *
718
	 * @param null|string $name The name of annotations to delete.
719
	 * @return bool
720
	 * @since 1.8
721
	 */
722 192 View Code Duplication
	public function deleteOwnedAnnotations($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
723
		// access is turned off for this because they might
724
		// no longer have access to an entity they created annotations on.
725 192
		$ia = elgg_set_ignore_access(true);
726
		$options = [
727 192
			'annotation_owner_guid' => $this->guid,
728 192
			'limit' => 0
729
		];
730 192
		if ($name) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $name of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
731
			$options['annotation_name'] = $name;
732
		}
733
734 192
		$r = elgg_delete_annotations($options);
735 192
		elgg_set_ignore_access($ia);
736 192
		return $r;
737
	}
738
739
	/**
740
	 * Disables annotations for this entity, optionally based on name.
741
	 *
742
	 * @param string $name An options name of annotations to disable.
743
	 * @return bool
744
	 * @since 1.8
745
	 */
746 5 View Code Duplication
	public function disableAnnotations($name = '') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
747
		$options = [
748 5
			'guid' => $this->guid,
749 5
			'limit' => 0
750
		];
751 5
		if ($name) {
752
			$options['annotation_name'] = $name;
753
		}
754
755 5
		return elgg_disable_annotations($options);
756
	}
757
758
	/**
759
	 * Enables annotations for this entity, optionally based on name.
760
	 *
761
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
762
	 *
763
	 * @param string $name An options name of annotations to enable.
764
	 * @return bool
765
	 * @since 1.8
766
	 */
767 3 View Code Duplication
	public function enableAnnotations($name = '') {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
768
		$options = [
769 3
			'guid' => $this->guid,
770 3
			'limit' => 0
771
		];
772 3
		if ($name) {
773
			$options['annotation_name'] = $name;
774
		}
775
776 3
		return elgg_enable_annotations($options);
777
	}
778
779
	/**
780
	 * Helper function to return annotation calculation results
781
	 *
782
	 * @param string $name        The annotation name.
783
	 * @param string $calculation A valid MySQL function to run its values through
784
	 * @return mixed
785
	 */
786 2
	private function getAnnotationCalculation($name, $calculation) {
787
		$options = [
788 2
			'guid' => $this->getGUID(),
789
			'distinct' => false,
790 2
			'annotation_name' => $name,
791 2
			'annotation_calculation' => $calculation
792
		];
793
794 2
		return elgg_get_annotations($options);
795
	}
796
797
	/**
798
	 * Adds an annotation to an entity.
799
	 *
800
	 * @warning By default, annotations are private.
801
	 *
802
	 * @warning Annotating an unsaved entity more than once with the same name
803
	 *          will only save the last annotation.
804
	 *
805
	 * @param string $name       Annotation name
806
	 * @param mixed  $value      Annotation value
807
	 * @param int    $access_id  Access ID
808
	 * @param int    $owner_guid GUID of the annotation owner
809
	 * @param string $vartype    The type of annotation value
810
	 *
811
	 * @return bool|int Returns int if an annotation is saved
812
	 */
813 123
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
814 123
		if ((int) $this->guid > 0) {
815 123
			return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
816
		} else {
817 24
			$this->temp_annotations[$name] = $value;
818
		}
819 24
		return true;
820
	}
821
822
	/**
823
	 * Gets an array of annotations.
824
	 *
825
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
826
	 * as the options array.
827
	 *
828
	 * @param array $options Array of options for elgg_get_annotations() except guid.
829
	 *
830
	 * @return array
831
	 * @see elgg_get_annotations()
832
	 */
833 9
	public function getAnnotations(array $options = []) {
834 9
		if ($this->guid) {
835 9
			$options['guid'] = $this->guid;
836
837 9
			return elgg_get_annotations($options);
838
		} else {
839
			$name = elgg_extract('annotation_name', $options, '');
840
841
			if (isset($this->temp_annotations[$name])) {
842
				return [$this->temp_annotations[$name]];
843
			}
844
		}
845
846
		return [];
847
	}
848
849
	/**
850
	 * Count annotations.
851
	 *
852
	 * @param string $name The type of annotation.
853
	 *
854
	 * @return int
855
	 */
856 2
	public function countAnnotations($name = "") {
857 2
		return $this->getAnnotationCalculation($name, 'count');
858
	}
859
860
	/**
861
	 * Get the average of an integer type annotation.
862
	 *
863
	 * @param string $name Annotation name
864
	 *
865
	 * @return int
866
	 */
867
	public function getAnnotationsAvg($name) {
868
		return $this->getAnnotationCalculation($name, 'avg');
869
	}
870
871
	/**
872
	 * Get the sum of integer type annotations of a given name.
873
	 *
874
	 * @param string $name Annotation name
875
	 *
876
	 * @return int
877
	 */
878
	public function getAnnotationsSum($name) {
879
		return $this->getAnnotationCalculation($name, 'sum');
880
	}
881
882
	/**
883
	 * Get the minimum of integer type annotations of given name.
884
	 *
885
	 * @param string $name Annotation name
886
	 *
887
	 * @return int
888
	 */
889
	public function getAnnotationsMin($name) {
890
		return $this->getAnnotationCalculation($name, 'min');
891
	}
892
893
	/**
894
	 * Get the maximum of integer type annotations of a given name.
895
	 *
896
	 * @param string $name Annotation name
897
	 *
898
	 * @return int
899
	 */
900
	public function getAnnotationsMax($name) {
901
		return $this->getAnnotationCalculation($name, 'max');
902
	}
903
904
	/**
905
	 * Count the number of comments attached to this entity.
906
	 *
907
	 * @return int Number of comments
908
	 * @since 1.8.0
909
	 */
910
	public function countComments() {
911
		$params = ['entity' => $this];
912
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
913
914
		if (is_int($num)) {
915
			return $num;
916
		} else {
917
			return elgg_get_entities([
918
				'type' => 'object',
919
				'subtype' => 'comment',
920
				'container_guid' => $this->getGUID(),
921
				'count' => true,
922
				'distinct' => false,
923
			]);
924
		}
925
	}
926
927
	/**
928
	 * Gets an array of entities with a relationship to this entity.
929
	 *
930
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
931
	 *                       for a list of options. 'relationship_guid' is set to
932
	 *                       this entity.
933
	 *
934
	 * @return array|false An array of entities or false on failure
935
	 * @see elgg_get_entities_from_relationship()
936
	 */
937
	public function getEntitiesFromRelationship(array $options = []) {
938
		$options['relationship_guid'] = $this->guid;
939
		return elgg_get_entities_from_relationship($options);
940
	}
941
942
	/**
943
	 * Gets the number of entities from a specific relationship type
944
	 *
945
	 * @param string $relationship         Relationship type (eg "friends")
946
	 * @param bool   $inverse_relationship Invert relationship
947
	 *
948
	 * @return int|false The number of entities or false on failure
949
	 */
950
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
951
		return elgg_get_entities_from_relationship([
952
			'relationship' => $relationship,
953
			'relationship_guid' => $this->getGUID(),
954
			'inverse_relationship' => $inverse_relationship,
955
			'count' => true
956
		]);
957
	}
958
959
	/**
960
	 * Can a user edit this entity?
961
	 *
962
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
963
	 *
964
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
965
	 *
966
	 * @return bool Whether this entity is editable by the given user.
967
	 * @see elgg_set_ignore_access()
968
	 */
969 107
	public function canEdit($user_guid = 0) {
970 107
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
971
	}
972
973
	/**
974
	 * Can a user delete this entity?
975
	 *
976
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
977
	 *
978
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
979
	 *
980
	 * @return bool Whether this entity is deletable by the given user.
981
	 * @since 1.11
982
	 * @see elgg_set_ignore_access()
983
	 */
984 211
	public function canDelete($user_guid = 0) {
985 211
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
986
	}
987
988
	/**
989
	 * Can a user edit metadata on this entity?
990
	 *
991
	 * If no specific metadata is passed, it returns whether the user can
992
	 * edit any metadata on the entity.
993
	 *
994
	 * @tip Can be overridden by by registering for the permissions_check:metadata
995
	 * plugin hook.
996
	 *
997
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
998
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
999
	 *
1000
	 * @return bool
1001
	 * @see elgg_set_ignore_access()
1002
	 */
1003 175
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1004 175
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1005
	}
1006
1007
	/**
1008
	 * Can a user add an entity to this container
1009
	 *
1010
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1011
	 * @param string $type      The type of entity we're looking to write
1012
	 * @param string $subtype   The subtype of the entity we're looking to write
1013
	 *
1014
	 * @return bool
1015
	 * @see elgg_set_ignore_access()
1016
	 */
1017 177
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1018 177
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1019
	}
1020
1021
	/**
1022
	 * Can a user comment on an entity?
1023
	 *
1024
	 * @tip Can be overridden by registering for the permissions_check:comment,
1025
	 * <entity type> plugin hook.
1026
	 *
1027
	 * @param int  $user_guid User guid (default is logged in user)
1028
	 * @param bool $default   Default permission
1029
	 * @return bool
1030
	 */
1031 2
	public function canComment($user_guid = 0, $default = null) {
1032 2
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1033
	}
1034
1035
	/**
1036
	 * Can a user annotate an entity?
1037
	 *
1038
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1039
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1040
	 *
1041
	 * @tip If you want logged out users to annotate an object, do not call
1042
	 * canAnnotate(). It's easier than using the plugin hook.
1043
	 *
1044
	 * @param int    $user_guid       User guid (default is logged in user)
1045
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1046
	 *
1047
	 * @return bool
1048
	 */
1049 8
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1050 8
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1051
	}
1052
1053
	/**
1054
	 * Returns the access_id.
1055
	 *
1056
	 * @return int The access ID
1057
	 */
1058
	public function getAccessID() {
1059
		return $this->access_id;
1060
	}
1061
1062
	/**
1063
	 * Returns the guid.
1064
	 *
1065
	 * @return int|null GUID
1066
	 */
1067 3614
	public function getGUID() {
1068 3614
		return $this->guid;
1069
	}
1070
1071
	/**
1072
	 * Returns the entity type
1073
	 *
1074
	 * @return string The entity type
1075
	 */
1076 1
	public function getType() {
1077
		// this is just for the PHPUnit mocking framework
1078 1
		return $this->type;
1079
	}
1080
1081
	/**
1082
	 * Get the entity subtype
1083
	 *
1084
	 * @return string The entity subtype
1085
	 */
1086 266
	public function getSubtype() {
1087
		// If this object hasn't been saved, then return the subtype string.
1088 266
		if ($this->attributes['guid']) {
1089 262
			return get_subtype_from_id($this->attributes['subtype']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression get_subtype_from_id($thi...attributes['subtype']); of type string|false adds false to the return on line 1089 which is incompatible with the return type declared by the interface Loggable::getSubtype of type string. It seems like you forgot to handle an error condition.
Loading history...
1090
		}
1091 7
		return $this->attributes['subtype'];
1092
	}
1093
1094
	/**
1095
	 * Get the guid of the entity's owner.
1096
	 *
1097
	 * @return int The owner GUID
1098
	 */
1099 68
	public function getOwnerGUID() {
1100 68
		return (int) $this->owner_guid;
1101
	}
1102
1103
	/**
1104
	 * Gets the \ElggEntity that owns this entity.
1105
	 *
1106
	 * @return \ElggEntity The owning entity
1107
	 */
1108 172
	public function getOwnerEntity() {
1109 172
		return get_entity($this->owner_guid);
1110
	}
1111
1112
	/**
1113
	 * Set the container for this object.
1114
	 *
1115
	 * @param int $container_guid The ID of the container.
1116
	 *
1117
	 * @return bool
1118
	 */
1119 1
	public function setContainerGUID($container_guid) {
1120 1
		return $this->container_guid = (int) $container_guid;
1121
	}
1122
1123
	/**
1124
	 * Gets the container GUID for this entity.
1125
	 *
1126
	 * @return int
1127
	 */
1128 179
	public function getContainerGUID() {
1129 179
		return (int) $this->container_guid;
1130
	}
1131
1132
	/**
1133
	 * Get the container entity for this object.
1134
	 *
1135
	 * @return \ElggEntity
1136
	 * @since 1.8.0
1137
	 */
1138 178
	public function getContainerEntity() {
1139 178
		return get_entity($this->getContainerGUID());
1140
	}
1141
1142
	/**
1143
	 * Returns the UNIX epoch time that this entity was last updated
1144
	 *
1145
	 * @return int UNIX epoch time
1146
	 */
1147 1
	public function getTimeUpdated() {
1148 1
		return $this->time_updated;
1149
	}
1150
1151
	/**
1152
	 * Gets the URL for this entity.
1153
	 *
1154
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1155
	 * customize the url for an entity.
1156
	 *
1157
	 * @return string The URL of the entity
1158
	 */
1159 10
	public function getURL() {
1160 10
		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
1161
1162 10
		if ($url === null || $url === '' || $url === false) {
1163 10
			return '';
1164
		}
1165
1166
		return elgg_normalize_url($url);
1167
	}
1168
1169
	/**
1170
	 * Saves icons using an uploaded file as the source.
1171
	 *
1172
	 * @param string $input_name Form input name
1173
	 * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1174
	 * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1175
	 * @return bool
1176
	 */
1177
	public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1178
		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1179
	}
1180
1181
	/**
1182
	 * Saves icons using a local file as the source.
1183
	 *
1184
	 * @param string $filename The full path to the local file
1185
	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1186
	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1187
	 * @return bool
1188
	 */
1189
	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1190
		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1191
	}
1192
1193
	/**
1194
	 * Saves icons using a file located in the data store as the source.
1195
	 *
1196
	 * @param string $file   An ElggFile instance
1197
	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1198
	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1199
	 * @return bool
1200
	 */
1201
	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1202
		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1203
	}
1204
1205
	/**
1206
	 * Returns entity icon as an ElggIcon object
1207
	 * The icon file may or may not exist on filestore
1208
	 *
1209
	 * @param string $size Size of the icon
1210
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1211
	 * @return \ElggIcon
1212
	 */
1213 5
	public function getIcon($size, $type = 'icon') {
1214 5
		return _elgg_services()->iconService->getIcon($this, $size, $type);
1215
	}
1216
1217
	/**
1218
	 * Removes all icon files and metadata for the passed type of icon.
1219
	 *
1220
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1221
	 * @return bool
1222
	 */
1223
	public function deleteIcon($type = 'icon') {
1224
		return _elgg_services()->iconService->deleteIcon($this, $type);
1225
	}
1226
1227
	/**
1228
	 * Returns the timestamp of when the icon was changed.
1229
	 *
1230
	 * @param string $size The size of the icon
1231
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1232
	 *
1233
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1234
	 */
1235
	public function getIconLastChange($size, $type = 'icon') {
1236
		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1237
	}
1238
1239
	/**
1240
	 * Returns if the entity has an icon of the passed type.
1241
	 *
1242
	 * @param string $size The size of the icon
1243
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1244
	 * @return bool
1245
	 */
1246
	public function hasIcon($size, $type = 'icon') {
1247
		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1248
	}
1249
1250
	/**
1251
	 * Get the URL for this entity's icon
1252
	 *
1253
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1254
	 * to customize the icon for an entity.
1255
	 *
1256
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1257
	 *                      or an array of parameters including 'size'
1258
	 * @return string The URL
1259
	 * @since 1.8.0
1260
	 */
1261 1
	public function getIconURL($params = []) {
1262 1
		return _elgg_services()->iconService->getIconURL($this, $params);
1263
	}
1264
1265
	/**
1266
	 * Save an entity.
1267
	 *
1268
	 * @return bool|int
1269
	 * @throws InvalidParameterException
1270
	 * @throws IOException
1271
	 */
1272 207
	public function save() {
1273 207
		$guid = $this->guid;
1274 207
		if ($guid > 0) {
1275 69
			$guid = $this->update();
1276
		} else {
1277 207
			$guid = $this->create();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->create(); of type false|integer adds the type integer to the return on line 1292 which is incompatible with the return type declared by the abstract method ElggData::save of type boolean.
Loading history...
1278 207
			if ($guid && !_elgg_services()->hooks->getEvents()->trigger('create', $this->type, $this)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $guid of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1279
				// plugins that return false to event don't need to override the access system
1280
				$ia = elgg_set_ignore_access(true);
1281
				$this->delete();
1282
				elgg_set_ignore_access($ia);
1283
				return false;
1284
			}
1285
		}
1286
1287 207
		if ($guid) {
1288 207
			_elgg_services()->entityCache->set($this);
1289 207
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1290
		}
1291
1292 207
		return $guid;
1293
	}
1294
1295
	/**
1296
	 * Create a new entry in the entities table.
1297
	 *
1298
	 * Saves the base information in the entities table for the entity.  Saving
1299
	 * the type-specific information is handled in the calling class method.
1300
	 *
1301
	 * @warning Entities must have an entry in both the entities table and their type table
1302
	 * or they will throw an exception when loaded.
1303
	 *
1304
	 * @return int The new entity's GUID
1305
	 * @throws InvalidParameterException If the entity's type has not been set.
1306
	 * @throws IOException If the new row fails to write to the DB.
1307
	 */
1308 207
	protected function create() {
1309
1310 207
		$type = $this->attributes['type'];
1311 207
		if (!in_array($type, \Elgg\Config::getEntityTypes())) {
1312
			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1313
					. implode(', ', \Elgg\Config::getEntityTypes()));
1314
		}
1315
1316 207
		$subtype = $this->attributes['subtype'];
1317 207
		$subtype_id = add_subtype($type, $subtype);
1318 207
		$owner_guid = (int) $this->attributes['owner_guid'];
1319 207
		$access_id = (int) $this->attributes['access_id'];
1320 207
		$now = $this->getCurrentTime()->getTimestamp();
1321 207
		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1322
1323 207
		$container_guid = $this->attributes['container_guid'];
1324 207
		if ($container_guid == 0) {
1325 70
			$container_guid = $owner_guid;
1326 70
			$this->attributes['container_guid'] = $container_guid;
1327
		}
1328 207
		$container_guid = (int) $container_guid;
1329
1330 207
		if ($access_id == ACCESS_DEFAULT) {
1331
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1332
		}
1333
1334 207
		$user_guid = elgg_get_logged_in_user_guid();
1335
1336
		// If given an owner, verify it can be loaded
1337 207 View Code Duplication
		if ($owner_guid) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1338 172
			$owner = $this->getOwnerEntity();
1339 172
			if (!$owner) {
1340
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1341
					. " owner $owner_guid could not be loaded.");
1342
				return false;
1343
			}
1344
1345
			// If different owner than logged in, verify can write to container.
1346
1347 172
			if ($user_guid != $owner_guid && !$owner->canWriteToContainer($user_guid, $type, $subtype)) {
1348
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1349
					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1350
				return false;
1351
			}
1352
		}
1353
1354
		// If given a container, verify it can be loaded and that the current user can write to it
1355 207 View Code Duplication
		if ($container_guid) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1356 173
			$container = $this->getContainerEntity();
1357 173
			if (!$container) {
1358
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1359
					. " container $container_guid could not be loaded.");
1360
				return false;
1361
			}
1362
1363 173
			if (!$container->canWriteToContainer($user_guid, $type, $subtype)) {
1364
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1365
					. " permitted to write to container $container_guid.");
1366
				return false;
1367
			}
1368
		}
1369
1370
		// Create primary table row
1371 207
		$guid = _elgg_services()->entityTable->insertRow((object) [
1372 207
			'type' => $type,
1373 207
			'subtype_id' => $subtype_id,
1374 207
			'owner_guid' => $owner_guid,
1375 207
			'container_guid' => $container_guid,
1376 207
			'access_id' => $access_id,
1377 207
			'time_created' => $time_created,
1378 207
			'time_updated' => $now,
1379 207
			'last_action' => $now,
1380 207
		], $this->attributes);
1381
1382 207
		if (!$guid) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $guid of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1383
			throw new \IOException("Unable to save new object's base entity information!");
1384
		}
1385
1386
		// We are writing this new entity to cache to make sure subsequent calls
1387
		// to get_entity() load the entity from cache and not from the DB. This
1388
		// MUST come before the metadata and annotation writes below!
1389 207
		_elgg_services()->entityCache->set($this);
1390
1391
		// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1392 207
		$this->attributes['subtype'] = (int) $subtype_id;
1393 207
		$this->attributes['guid'] = (int) $guid;
1394 207
		$this->attributes['time_created'] = (int) $time_created;
1395 207
		$this->attributes['time_updated'] = (int) $now;
1396 207
		$this->attributes['last_action'] = (int) $now;
1397 207
		$this->attributes['container_guid'] = (int) $container_guid;
1398
1399
		// Save any unsaved metadata
1400 207
		if (sizeof($this->temp_metadata) > 0) {
1401 163
			foreach ($this->temp_metadata as $name => $value) {
1402 163
				if (count($value) == 1) {
1403
					// temp metadata is always an array, but if there is only one value return just the value
1404 163
					$this->$name = $value[0];
1405
				} else {
1406 163
					$this->$name = $value;
1407
				}
1408
			}
1409
1410 163
			$this->temp_metadata = [];
1411
		}
1412
1413
		// Save any unsaved annotations.
1414 207
		if (sizeof($this->temp_annotations) > 0) {
1415 24
			foreach ($this->temp_annotations as $name => $value) {
1416 24
				$this->annotate($name, $value);
1417
			}
1418
1419 24
			$this->temp_annotations = [];
1420
		}
1421
1422
		// Save any unsaved private settings.
1423 207
		if (sizeof($this->temp_private_settings) > 0) {
1424 29
			foreach ($this->temp_private_settings as $name => $value) {
1425 29
				$this->setPrivateSetting($name, $value);
1426
			}
1427
1428 29
			$this->temp_private_settings = [];
1429
		}
1430
1431 207
		return $guid;
1432
	}
1433
1434
	/**
1435
	 * Update the entity in the database.
1436
	 *
1437
	 * @return bool Whether the update was successful.
1438
	 *
1439
	 * @throws InvalidParameterException
1440
	 */
1441 69
	protected function update() {
1442
1443 69
		_elgg_services()->boot->invalidateCache();
1444
1445 69
		if (!$this->canEdit()) {
1446 1
			return false;
1447
		}
1448
1449
		// give old update event a chance to stop the update
1450 69
		if (!_elgg_services()->hooks->getEvents()->trigger('update', $this->type, $this)) {
1451
			return false;
1452
		}
1453
1454
		// See #6225. We copy these after the update event in case a handler changed one of them.
1455 69
		$guid = (int) $this->guid;
1456 69
		$owner_guid = (int) $this->owner_guid;
1457 69
		$access_id = (int) $this->access_id;
1458 69
		$container_guid = (int) $this->container_guid;
1459 69
		$time_created = (int) $this->time_created;
1460 69
		$time = $this->getCurrentTime()->getTimestamp();
1461
1462 69
		if ($access_id == ACCESS_DEFAULT) {
1463
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1464
		}
1465
1466
		// Update primary table
1467 69
		$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1468 69
			'owner_guid' => $owner_guid,
1469 69
			'container_guid' => $container_guid,
1470 69
			'access_id' => $access_id,
1471 69
			'time_created' => $time_created,
1472 69
			'time_updated' => $time,
1473 69
			'guid' => $guid,
1474
		]);
1475 69
		if ($ret === false) {
1476
			return false;
1477
		}
1478
1479 69
		$this->attributes['time_updated'] = $time;
1480
1481 69
		elgg_trigger_after_event('update', $this->type, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<ElggEntity>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1482
1483
		// TODO(evan): Move this to \ElggObject?
1484 69
		if ($this instanceof \ElggObject) {
1485 9
			update_river_access_by_object($guid, $access_id);
1486
		}
1487
1488 69
		$this->orig_attributes = [];
1489
1490
		// Handle cases where there was no error BUT no rows were updated!
1491 69
		return true;
1492
	}
1493
1494
	/**
1495
	 * Loads attributes from the entities table into the object.
1496
	 *
1497
	 * @param \stdClass $row Object of properties from database row(s)
1498
	 *
1499
	 * @return bool
1500
	 */
1501 3711
	protected function load(\stdClass $row) {
1502 3711
		$type = $this->type;
1503
1504 3711
		$attr_loader = new \Elgg\AttributeLoader(get_class($this), $type, $this->attributes);
1505 3711
		if ($type === 'user' || $this instanceof ElggPlugin) {
1506 504
			$attr_loader->requires_access_control = false;
1507
		}
1508
1509 3711
		$attrs = $attr_loader->getRequiredAttributes($row);
1510 3711
		if (!$attrs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attrs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1511
			return false;
1512
		}
1513
1514 3711
		$this->attributes = $attrs;
1515
1516 3711
		foreach ($attr_loader->getAdditionalSelectValues() as $name => $value) {
1517 18
			$this->setVolatileData("select:$name", $value);
1518
		}
1519
1520 3711
		_elgg_services()->entityCache->set($this);
1521
1522 3711
		return true;
1523
	}
1524
1525
	/**
1526
	 * Load new data from database into existing entity. Overwrites data but
1527
	 * does not change values not included in the latest data.
1528
	 *
1529
	 * @internal This is used when the same entity is selected twice during a
1530
	 * request in case different select clauses were used to load different data
1531
	 * into volatile data.
1532
	 *
1533
	 * @param \stdClass $row DB row with new entity data
1534
	 * @return bool
1535
	 * @access private
1536
	 */
1537
	public function refresh(\stdClass $row) {
1538
		if ($row instanceof \stdClass) {
1539
			return $this->load($row);
1540
		}
1541
		return false;
1542
	}
1543
1544
	/**
1545
	 * Disable this entity.
1546
	 *
1547
	 * Disabled entities are not returned by getter functions.
1548
	 * To enable an entity, use {@link \ElggEntity::enable()}.
1549
	 *
1550
	 * Recursively disabling an entity will disable all entities
1551
	 * owned or contained by the parent entity.
1552
	 *
1553
	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1554
	 *
1555
	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1556
	 *
1557
	 * @param string $reason    Optional reason
1558
	 * @param bool   $recursive Recursively disable all contained entities?
1559
	 *
1560
	 * @return bool
1561
	 * @see \ElggEntity::enable()
1562
	 */
1563 5
	public function disable($reason = "", $recursive = true) {
1564 5
		if (!$this->guid) {
1565
			return false;
1566
		}
1567
1568 5
		if (!_elgg_services()->hooks->getEvents()->trigger('disable', $this->type, $this)) {
1569
			return false;
1570
		}
1571
1572 5
		if (!$this->canEdit()) {
1573
			return false;
1574
		}
1575
1576 5
		if ($this instanceof ElggUser && !$this->isBanned()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ElggEntity as the method isBanned() does only exist in the following sub-classes of ElggEntity: ElggUser. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1577
			// temporarily ban to prevent using the site during disable
1578 1
			$this->ban();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ElggEntity as the method ban() does only exist in the following sub-classes of ElggEntity: ElggUser. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1579 1
			$unban_after = true;
1580
		} else {
1581 4
			$unban_after = false;
1582
		}
1583
1584 5
		if ($reason) {
1585
			$this->disable_reason = $reason;
1 ignored issue
show
Documentation introduced by
The property disable_reason does not exist on object<ElggEntity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1586
		}
1587
1588 5
		$dbprefix = _elgg_config()->dbprefix;
1589
1590 5
		$guid = (int) $this->guid;
1591
1592 5
		if ($recursive) {
1593
			// Only disable enabled subentities
1594 5
			$hidden = access_get_show_hidden_status();
1595 5
			access_show_hidden_entities(false);
1596
1597 5
			$ia = elgg_set_ignore_access(true);
1598
1599
			$base_options = [
1600 5
				'wheres' => [
1601 5
					"e.guid != $guid",
1602
				],
1603
				'limit' => false,
1604
			];
1605
1606 5
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1607 5
				$options = $base_options;
1608 5
				$options[$db_column] = $guid;
1609
1610 5
				$subentities = new \ElggBatch('elgg_get_entities', $options);
1611 5
				$subentities->setIncrementOffset(false);
1612
1613 5
				foreach ($subentities as $subentity) {
1614
					/* @var $subentity \ElggEntity */
1615 2
					if (!$subentity->isEnabled()) {
1616
						continue;
1617
					}
1618 2
					add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1619 5
					$subentity->disable($reason);
1620
				}
1621
			}
1622
1623 5
			access_show_hidden_entities($hidden);
1624 5
			elgg_set_ignore_access($ia);
1625
		}
1626
1627 5
		$this->disableMetadata();
1628 5
		$this->disableAnnotations();
1629
1630 5
		_elgg_services()->entityCache->remove($guid);
1631 5
		_elgg_get_memcache('new_entity_cache')->delete($guid);
1632
1633
		$sql = "
1634 5
			UPDATE {$dbprefix}entities
1635
			SET enabled = 'no'
1636
			WHERE guid = :guid
1637
		";
1638
		$params = [
1639 5
			':guid' => $guid,
1640
		];
1641 5
		$disabled = $this->getDatabase()->updateData($sql, false, $params);
1642
1643 5
		if ($unban_after) {
1644 1
			$this->unban();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ElggEntity as the method unban() does only exist in the following sub-classes of ElggEntity: ElggUser. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1645
		}
1646
1647 5 View Code Duplication
		if ($disabled) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1648 5
			$this->attributes['enabled'] = 'no';
1649 5
			_elgg_services()->hooks->getEvents()->trigger('disable:after', $this->type, $this);
1650
		}
1651
1652 5
		return (bool) $disabled;
1653
	}
1654
1655
	/**
1656
	 * Enable the entity
1657
	 *
1658
	 * @warning Disabled entities can't be loaded unless
1659
	 * {@link access_show_hidden_entities(true)} has been called.
1660
	 *
1661
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
1662
	 * @see access_show_hiden_entities()
1663
	 * @return bool
1664
	 */
1665 3
	public function enable($recursive = true) {
1666 3
		$guid = (int) $this->guid;
1667 3
		if (!$guid) {
1668
			return false;
1669
		}
1670
1671 3
		if (!_elgg_services()->hooks->getEvents()->trigger('enable', $this->type, $this)) {
1672
			return false;
1673
		}
1674
1675 3
		if (!$this->canEdit()) {
1676
			return false;
1677
		}
1678
1679
		// Override access only visible entities
1680 3
		$old_access_status = access_get_show_hidden_status();
1681 3
		access_show_hidden_entities(true);
1682
1683 3
		$db = $this->getDatabase();
1684 3
		$result = $db->updateData("
1685 3
			UPDATE {$db->prefix}entities
1686
			SET enabled = 'yes'
1687 3
			WHERE guid = $guid
1688
		");
1689
1690 3
		$this->deleteMetadata('disable_reason');
1691 3
		$this->enableMetadata();
1692 3
		$this->enableAnnotations();
1693
1694 3
		if ($recursive) {
1695 3
			$disabled_with_it = elgg_get_entities_from_relationship([
1696 3
				'relationship' => 'disabled_with',
1697 3
				'relationship_guid' => $guid,
1698
				'inverse_relationship' => true,
1699 3
				'limit' => 0,
1700
			]);
1701
1702 3
			foreach ($disabled_with_it as $e) {
0 ignored issues
show
Bug introduced by
The expression $disabled_with_it of type false|object<ElggBatch>|integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1703 1
				$e->enable();
1704 1
				remove_entity_relationship($e->guid, 'disabled_with', $guid);
1705
			}
1706
		}
1707
1708 3
		access_show_hidden_entities($old_access_status);
1709
1710 3 View Code Duplication
		if ($result) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1711 3
			$this->attributes['enabled'] = 'yes';
1712 3
			_elgg_services()->hooks->getEvents()->trigger('enable:after', $this->type, $this);
1713
		}
1714
1715 3
		return $result;
1716
	}
1717
1718
	/**
1719
	 * Is this entity enabled?
1720
	 *
1721
	 * @return boolean Whether this entity is enabled.
1722
	 */
1723 2
	public function isEnabled() {
1724 2
		return $this->enabled == 'yes';
1725
	}
1726
1727
	/**
1728
	 * Deletes the entity.
1729
	 *
1730
	 * Removes the entity and its metadata, annotations, relationships,
1731
	 * river entries, and private data.
1732
	 *
1733
	 * Optionally can remove entities contained and owned by this entity.
1734
	 *
1735
	 * @warning If deleting recursively, this bypasses ownership of items contained by
1736
	 * the entity.  That means that if the container_guid = $this->guid, the item will
1737
	 * be deleted regardless of who owns it.
1738
	 *
1739
	 * @param bool $recursive If true (default) then all entities which are
1740
	 *                        owned or contained by $this will also be deleted.
1741
	 *
1742
	 * @return bool
1743
	 */
1744 208
	public function delete($recursive = true) {
1745
1746 208
		$guid = $this->guid;
1747 208
		if (!$guid) {
1748 1
			return false;
1749
		}
1750
1751
		// first check if we can delete this entity
1752
		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
1753
		// which could potentially remove some content if the user didn't have access
1754 208
		if (!$this->canDelete()) {
1755 21
			return false;
1756
		}
1757
1758
		// now trigger an event to let others know this entity is about to be deleted
1759
		// so they can prevent it or take their own actions
1760 192
		if (!_elgg_services()->hooks->getEvents()->trigger('delete', $this->type, $this)) {
1761
			return false;
1762
		}
1763
1764 192
		if ($this instanceof ElggUser) {
1765
			// ban to prevent using the site during delete
1766 62
			$this->ban();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ElggEntity as the method ban() does only exist in the following sub-classes of ElggEntity: ElggUser. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1767
		}
1768
1769
		// Delete contained owned and otherwise releated objects (depth first)
1770 192
		if ($recursive) {
1771
			// Temporarily overriding access controls
1772 192
			$entity_disable_override = access_get_show_hidden_status();
1773 192
			access_show_hidden_entities(true);
1774 192
			$ia = elgg_set_ignore_access(true);
1775
1776
			// @todo there was logic in the original code that ignored
1777
			// entities with owner or container guids of themselves.
1778
			// this should probably be prevented in \ElggEntity instead of checked for here
1779
			$base_options = [
1780 192
				'wheres' => [
1781 192
					"e.guid != $guid",
1782
				],
1783
				'limit' => false,
1784
			];
1785
1786 192
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1787 192
				$options = $base_options;
1788 192
				$options[$db_column] = $guid;
1789
1790 192
				$batch = new \ElggBatch('elgg_get_entities', $options);
1791 192
				$batch->setIncrementOffset(false);
1792
1793
				/* @var $e \ElggEntity */
1794 192
				foreach ($batch as $e) {
1795 192
					$e->delete(true);
1796
				}
1797
			}
1798
1799 192
			access_show_hidden_entities($entity_disable_override);
1800 192
			elgg_set_ignore_access($ia);
1801
		}
1802
1803 192
		$entity_disable_override = access_get_show_hidden_status();
1804 192
		access_show_hidden_entities(true);
1805 192
		$ia = elgg_set_ignore_access(true);
1806
1807
		// Now delete the entity itself
1808 192
		$this->deleteMetadata();
1809 192
		$this->deleteOwnedMetadata();
1810 192
		$this->deleteAnnotations();
1811 192
		$this->deleteOwnedAnnotations();
1812 192
		$this->deleteRelationships();
1813 192
		$this->deleteAccessCollectionMemberships();
1814 192
		$this->deleteOwnedAccessCollections();
1815
1816 192
		access_show_hidden_entities($entity_disable_override);
1817 192
		elgg_set_ignore_access($ia);
1818
1819 192
		elgg_delete_river(['subject_guid' => $guid, 'limit' => false]);
1820 192
		elgg_delete_river(['object_guid' => $guid, 'limit' => false]);
1821 192
		elgg_delete_river(['target_guid' => $guid, 'limit' => false]);
1822
1823 192
		remove_all_private_settings($guid);
1824
1825 192
		_elgg_invalidate_cache_for_entity($guid);
1826 192
		_elgg_invalidate_memcache_for_entity($guid);
1827
1828 192
		$dbprefix = _elgg_config()->dbprefix;
1829
1830
		$sql = "
1831 192
			DELETE FROM {$dbprefix}entities
1832
			WHERE guid = :guid
1833
		";
1834
		$params = [
1835 192
			':guid' => $guid,
1836
		];
1837
1838 192
		$deleted = $this->getDatabase()->deleteData($sql, $params);
1839
1840 192
		$this->clearAllFiles();
1841
1842 192
		return (bool) $deleted;
1843
	}
1844
1845
	/**
1846
	 * Removes all entity files in the dataroot
1847
	 *
1848
	 * @warning This only deletes the physical files and not their entities.
1849
	 *
1850
	 * @return bool
1851
	 */
1852 192
	protected function clearAllFiles() {
1853 192
		$dir = new \Elgg\EntityDirLocator($this->guid);
1854 192
		$file_path = _elgg_config()->dataroot . $dir;
1855 192
		return delete_directory($file_path);
1856
	}
1857
1858
	/**
1859
	 * {@inheritdoc}
1860
	 */
1861 1 View Code Duplication
	public function toObject() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1862 1
		$object = $this->prepareObject(new \stdClass());
1863 1
		$params = ['entity' => $this];
1864 1
		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1865 1
		return $object;
1866
	}
1867
1868
	/**
1869
	 * Prepare an object copy for toObject()
1870
	 *
1871
	 * @param \stdClass $object Object representation of the entity
1872
	 * @return \stdClass
1873
	 */
1874 1
	protected function prepareObject($object) {
1875 1
		$object->guid = $this->guid;
1876 1
		$object->type = $this->getType();
1877 1
		$object->subtype = $this->getSubtype();
1878 1
		$object->owner_guid = $this->getOwnerGUID();
1879 1
		$object->container_guid = $this->getContainerGUID();
1880 1
		$object->time_created = date('c', $this->getTimeCreated());
1881 1
		$object->time_updated = date('c', $this->getTimeUpdated());
1882 1
		$object->url = $this->getURL();
1883 1
		$object->read_access = (int) $this->access_id;
1884 1
		return $object;
1885
	}
1886
1887
	/*
1888
	 * LOCATABLE INTERFACE
1889
	 */
1890
1891
	/**
1892
	 * Gets the 'location' metadata for the entity
1893
	 *
1894
	 * @return string The location
1895
	 */
1896
	public function getLocation() {
1897
		return $this->location;
1898
	}
1899
1900
	/**
1901
	 * Sets the 'location' metadata for the entity
1902
	 *
1903
	 * @param string $location String representation of the location
1904
	 *
1905
	 * @return void
1906
	 */
1907
	public function setLocation($location) {
1908
		$this->location = $location;
1909
	}
1910
1911
	/**
1912
	 * Set latitude and longitude metadata tags for a given entity.
1913
	 *
1914
	 * @param float $lat  Latitude
1915
	 * @param float $long Longitude
1916
	 *
1917
	 * @return void
1918
	 * @todo Unimplemented
1919
	 */
1920
	public function setLatLong($lat, $long) {
1921
		$this->{"geo:lat"} = $lat;
1922
		$this->{"geo:long"} = $long;
1923
	}
1924
1925
	/**
1926
	 * Return the entity's latitude.
1927
	 *
1928
	 * @return float
1929
	 * @todo Unimplemented
1930
	 */
1931
	public function getLatitude() {
1932
		return (float) $this->{"geo:lat"};
1933
	}
1934
1935
	/**
1936
	 * Return the entity's longitude
1937
	 *
1938
	 * @return float
1939
	 * @todo Unimplemented
1940
	 */
1941
	public function getLongitude() {
1942
		return (float) $this->{"geo:long"};
1943
	}
1944
1945
	/*
1946
	 * SYSTEM LOG INTERFACE
1947
	 */
1948
1949
	/**
1950
	 * Return an identification for the object for storage in the system log.
1951
	 * This id must be an integer.
1952
	 *
1953
	 * @return int
1954
	 */
1955 161
	public function getSystemLogID() {
1956 161
		return $this->getGUID();
1957
	}
1958
1959
	/**
1960
	 * For a given ID, return the object associated with it.
1961
	 * This is used by the system log. It can be called on any Loggable object.
1962
	 *
1963
	 * @param int $id GUID.
1964
	 * @return int GUID
1965
	 */
1966
	public function getObjectFromID($id) {
1967
		return get_entity($id);
1968
	}
1969
1970
	/**
1971
	 * Returns tags for this entity.
1972
	 *
1973
	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
1974
	 *
1975
	 * @param array $tag_names Optionally restrict by tag metadata names.
1976
	 *
1977
	 * @return array
1978
	 */
1979
	public function getTags($tag_names = null) {
1980
		if ($tag_names && !is_array($tag_names)) {
1981
			$tag_names = [$tag_names];
1982
		}
1983
1984
		$valid_tags = elgg_get_registered_tag_metadata_names();
1985
		$entity_tags = [];
1986
1987
		foreach ($valid_tags as $tag_name) {
1988
			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
1989
				continue;
1990
			}
1991
1992
			if ($tags = $this->$tag_name) {
1993
				// if a single tag, metadata returns a string.
1994
				// if multiple tags, metadata returns an array.
1995
				if (is_array($tags)) {
1996
					$entity_tags = array_merge($entity_tags, $tags);
1997
				} else {
1998
					$entity_tags[] = $tags;
1999
				}
2000
			}
2001
		}
2002
2003
		return $entity_tags;
2004
	}
2005
2006
	/**
2007
	 * Remove the membership of all access collections for this entity (if the entity is a user)
2008
	 *
2009
	 * @return bool
2010
	 * @since 1.11
2011
	 */
2012 192
	public function deleteAccessCollectionMemberships() {
2013
2014 192
		if (!$this->guid) {
2015
			return false;
2016
		}
2017
2018 192
		if ($this->type !== 'user') {
2019 167
			return true;
2020
		}
2021
2022 62
		$ac = _elgg_services()->accessCollections;
2023
2024 62
		$collections = $ac->getCollectionsByMember($this->guid);
2025 62
		if (empty($collections)) {
2026 62
			return true;
2027
		}
2028
2029 3
		$result = true;
2030 3
		foreach ($collections as $collection) {
2031 3
			$result = $result & $ac->removeUser($this->guid, $collection->id);
2032
		}
2033
2034 3
		return $result;
2035
	}
2036
2037
	/**
2038
	 * Remove all access collections owned by this entity
2039
	 *
2040
	 * @return bool
2041
	 * @since 1.11
2042
	 */
2043 192
	public function deleteOwnedAccessCollections() {
2044
2045 192
		if (!$this->guid) {
2046
			return false;
2047
		}
2048
2049 192
		$ac = _elgg_services()->accessCollections;
2050
2051 192
		$collections = $ac->getEntityCollections($this->guid);
2052 192
		if (empty($collections)) {
2053 192
			return true;
2054
		}
2055
2056 4
		$result = true;
2057 4
		foreach ($collections as $collection) {
2058 4
			$result = $result & $ac->delete($collection->id);
2059
		}
2060
2061 4
		return $result;
2062
	}
2063
2064
	/**
2065
	 * Update the last_action column in the entities table.
2066
	 *
2067
	 * @warning This is different to time_updated.  Time_updated is automatically set,
2068
	 * while last_action is only set when explicitly called.
2069
	 *
2070
	 * @param int $posted Timestamp of last action
2071
	 * @return int|false
2072
	 * @access private
2073
	 */
2074 10
	public function updateLastAction($posted = null) {
2075 10
		$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2076 10
		if ($posted) {
2077 10
			$this->attributes['last_action'] = $posted;
2078 10
			_elgg_services()->entityCache->set($this);
2079 10
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
2080
		}
2081 10
		return $posted;
2082
	}
2083
}
2084