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

ElggEntity::enable()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 52
Code Lines 31

Duplication

Lines 4
Ratio 7.69 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 31
nc 7
nop 1
dl 4
loc 52
ccs 0
cts 27
cp 0
crap 56
rs 7.2396
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	public function __construct(\stdClass $row = null) {
91
		$this->initializeAttributes();
92
93
		if ($row && !$this->load($row)) {
94
			$msg = "Failed to load new " . get_class() . " for GUID:" . $row->guid;
95 329
			throw new \IOException($msg);
96 329
		}
97
	}
98 329
99
	/**
100
	 * Initialize the attributes array.
101
	 *
102 329
	 * This is vital to distinguish between metadata and base parameters.
103
	 *
104
	 * @return void
105
	 */
106
	protected function initializeAttributes() {
107
		parent::initializeAttributes();
108
109
		$this->attributes['guid'] = null;
110
		$this->attributes['type'] = null;
111 329
		$this->attributes['subtype'] = null;
112 329
113
		$this->attributes['owner_guid'] = _elgg_services()->session->getLoggedInUserGuid();
114 329
		$this->attributes['container_guid'] = _elgg_services()->session->getLoggedInUserGuid();
115 329
116 329
		$this->attributes['access_id'] = ACCESS_PRIVATE;
117
		$this->attributes['time_updated'] = null;
118 329
		$this->attributes['last_action'] = null;
119 329
		$this->attributes['enabled'] = "yes";
120
121 329
		$this->attributes['type'] = $this->getType();
122 329
		$this->attributes += self::getExtraAttributeDefaults($this->getType());
123 329
	}
124 329
125
	/**
126 329
	 * Clone an entity
127 329
	 *
128 329
	 * Resets the guid so that the entity can be saved as a distinct entity from
129
	 * the original. Creation time will be set when this new entity is saved.
130
	 * The owner and container guids come from the original entity. The clone
131
	 * method copies metadata but does not copy annotations or private settings.
132
	 *
133
	 * @note metadata will have its owner and access id set when the entity is saved
134
	 * and it will be the same as that of the entity.
135
	 *
136
	 * @return void
137
	 */
138
	public function __clone() {
139
		$orig_entity = get_entity($this->guid);
140
		if (!$orig_entity) {
141
			_elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
142
			return;
143 1
		}
144 1
145 1
		$metadata_array = elgg_get_metadata([
146
			'guid' => $this->guid,
147
			'limit' => 0
148
		]);
149
150 1
		$this->attributes['guid'] = null;
151 1
		$this->attributes['time_created'] = null;
152 1
		$this->attributes['time_updated'] = null;
153
		$this->attributes['last_action'] = null;
154
155 1
		$this->attributes['subtype'] = $orig_entity->getSubtype();
156 1
157 1
		// copy metadata over to new entity - slightly convoluted due to
158 1
		// handling of metadata arrays
159
		if (is_array($metadata_array)) {
160 1
			// create list of metadata names
161
			$metadata_names = [];
162
			foreach ($metadata_array as $metadata) {
163
				$metadata_names[] = $metadata['name'];
164 1
			}
165
			// arrays are stored with multiple enties per name
166 1
			$metadata_names = array_unique($metadata_names);
167 1
168
			// move the metadata over
169
			foreach ($metadata_names as $name) {
170
				$this->__set($name, $orig_entity->$name);
171 1
			}
172
		}
173
	}
174 1
175
	/**
176
	 * Set an attribute or metadata value for this entity
177
	 *
178 1
	 * Anything that is not an attribute is saved as metadata.
179
	 *
180
	 * @warning Metadata set this way will inherit the entity's owner and
181
	 * access ID. If you want more control over metadata, use \ElggEntity::setMetadata()
182
	 *
183
	 * @param string $name  Name of the attribute or metadata
184
	 * @param mixed  $value The value to be set
185
	 * @return void
186
	 * @see \ElggEntity::setMetadata()
187
	 */
188
	public function __set($name, $value) {
189
		if ($this->$name === $value) {
190
			// quick return if value is not changing
191
			return;
192
		}
193 264
194 264
		if (array_key_exists($name, $this->attributes)) {
195
			// if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
196 203
			if (is_int($this->attributes[$name])
197
					&& is_string($value)
198
					&& ((string) $this->attributes[$name] === $value)) {
199 120
				return;
200
			}
201 115
202 115
			// Due to https://github.com/Elgg/Elgg/pull/5456#issuecomment-17785173, certain attributes
203 115
			// will store empty strings as null in the DB. In the somewhat common case that we're re-setting
204
			// the value to empty string, don't consider this a change.
205
			if (in_array($name, ['title', 'name', 'description'])
206
					&& $this->attributes[$name] === null
207
					&& $value === "") {
208
				return;
209
			}
210 115
211 115
			// keep original values
212 115
			if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
213
				$this->orig_attributes[$name] = $this->attributes[$name];
214
			}
215
216
			// Certain properties should not be manually changed!
217 115
			switch ($name) {
218 5
				case 'guid':
219
				case 'time_updated':
220
				case 'last_action':
221
					return;
222
					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...
223 115
				case 'access_id':
224 115
				case 'owner_guid':
225 115 View Code Duplication
				case '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...
226 3
					if ($value !== null) {
227
						$this->attributes[$name] = (int) $value;
228 112
					} else {
229 109
						$this->attributes[$name] = null;
230 16
					}
231 101
					break;
232 101
				default:
233
					$this->attributes[$name] = $value;
234
					break;
235
			}
236 101
			return;
237
		}
238 15
239 15
		$this->setMetadata($name, $value);
240
	}
241 112
242
	/**
243
	 * Get the original values of attribute(s) that have been modified since the entity was persisted.
244 106
	 *
245 106
	 * @return array
246
	 */
247
	public function getOriginalAttributes() {
248
		return $this->orig_attributes;
249
	}
250
251
	/**
252 22
	 * Get an attribute or metadata value
253 22
	 *
254
	 * If the name matches an attribute, the attribute is returned. If metadata
255
	 * does not exist with that name, a null is returned.
256
	 *
257
	 * This only returns an array if there are multiple values for a particular
258
	 * $name key.
259
	 *
260
	 * @param string $name Name of the attribute or metadata
261
	 * @return mixed
262
	 */
263
	public function __get($name) {
264
		if (array_key_exists($name, $this->attributes)) {
265
			if ($name === 'subtype' && $this->attributes['guid']) {
266
				_elgg_services()->logger->warn('Reading ->subtype on a persisted entity is unreliable.');
267
			}
268 324
			return $this->attributes[$name];
269 324
		}
270 324
271 261
		return $this->getMetadata($name);
272
	}
273 324
274
	/**
275
	 * Get the entity's display name
276 131
	 *
277
	 * @return string The title or name of this entity.
278
	 */
279
	public function getDisplayName() {
280
		$attr = $this->getSecondaryTableColumns()[0];
281
		return $this->$attr;
282
	}
283
284 8
	/**
285 8
	 * Sets the title or name of this entity.
286 8
	 *
287
	 * @param string $display_name The title or name of this entity.
288
	 * @return void
289
	 */
290
	public function setDisplayName($display_name) {
291
		$attr = $this->getSecondaryTableColumns()[0];
292
		$this->$attr = $display_name;
293
	}
294
295 1
	/**
296 1
	 * Return the value of a piece of metadata.
297 1
	 *
298 1
	 * @param string $name Name
299
	 *
300
	 * @return mixed The value, or null if not found.
301
	 */
302
	public function getMetadata($name) {
303
		$guid = $this->guid;
304
305
		if (!$guid) {
306
			if (isset($this->temp_metadata[$name])) {
307 131
				// md is returned as an array only if more than 1 entry
308 131
				if (count($this->temp_metadata[$name]) == 1) {
309
					return $this->temp_metadata[$name][0];
310 131
				} else {
311 103
					return $this->temp_metadata[$name];
312
				}
313 95
			} else {
314 95
				return null;
315
			}
316
		}
317
318
		// upon first cache miss, just load/cache all the metadata and retry.
319 103
		// if this works, the rest of this function may not be needed!
320
		$cache = _elgg_services()->metadataCache;
321
		if ($cache->isLoaded($guid)) {
322
			return $cache->getSingle($guid, $name);
323
		} else {
324
			$cache->populateFromEntities([$guid]);
325 72
			// in case ignore_access was on, we have to check again...
326 72
			if ($cache->isLoaded($guid)) {
327 53
				return $cache->getSingle($guid, $name);
328
			}
329 72
		}
330
331 72
		$md = elgg_get_metadata([
332 72
			'guid' => $guid,
333
			'metadata_name' => $name,
334
			'limit' => 0,
335
			'distinct' => false,
336
		]);
337
338
		$value = null;
339
340
		if ($md && !is_array($md)) {
341
			$value = $md->value;
342
		} elseif (count($md) == 1) {
343
			$value = $md[0]->value;
344
		} else if ($md && is_array($md)) {
345
			$value = metadata_array_to_values($md);
346
		}
347
348
		return $value;
349
	}
350
351
	/**
352
	 * Unset a property from metadata or attribute.
353
	 *
354
	 * @warning If you use this to unset an attribute, you must save the object!
355
	 *
356
	 * @param string $name The name of the attribute or metadata.
357
	 *
358
	 * @return void
359
	 * @todo some attributes should be set to null or other default values
360
	 */
361
	public function __unset($name) {
362
		if (array_key_exists($name, $this->attributes)) {
363
			$this->attributes[$name] = "";
364
		} else {
365
			$this->deleteMetadata($name);
366 58
		}
367 58
	}
368 1
369
	/**
370 57
	 * Set metadata on this entity.
371
	 *
372 58
	 * Plugin developers usually want to use the magic set method ($entity->name = 'value').
373
	 * Use this method if you want to explicitly set the owner or access of the metadata.
374
	 * You cannot set the owner/access before the entity has been saved.
375
	 *
376
	 * @param string $name       Name of the metadata
377
	 * @param mixed  $value      Value of the metadata (doesn't support assoc arrays)
378
	 * @param string $value_type 'text', 'integer', or '' for automatic detection
379
	 * @param bool   $multiple   Allow multiple values for a single name.
380
	 *                           Does not support associative arrays.
381
	 * @param int    $owner_guid GUID of entity that owns the metadata.
382
	 *                           Default is owner of entity.
383
	 *
384
	 * @return bool
385
	 * @throws InvalidArgumentException
386
	 */
387
	public function setMetadata($name, $value, $value_type = '', $multiple = false, $owner_guid = 0) {
388
389
		// normalize value to an array that we will loop over
390
		// remove indexes if value already an array.
391
		if (is_array($value)) {
392 106
			$value = array_values($value);
393
		} else {
394
			$value = [$value];
395
		}
396 106
397 1
		// saved entity. persist md to db.
398
		if ($this->guid) {
399 106
			// if overwriting, delete first.
400
			if (!$multiple) {
401
				$options = [
402
					'guid' => $this->getGUID(),
403 106
					'metadata_name' => $name,
404
					'limit' => 0
405 51
				];
406
				// @todo in 1.9 make this return false if can't add metadata
407 51
				// https://github.com/elgg/elgg/issues/4520
408 51
				//
409 51
				// need to remove access restrictions right now to delete
410
				// because this is the expected behavior
411
				$ia = elgg_set_ignore_access(true);
412
				if (false === elgg_delete_metadata($options)) {
413
					return false;
414
				}
415
				elgg_set_ignore_access($ia);
416 51
			}
417 51
418 51
			$owner_guid = $owner_guid ? (int) $owner_guid : $this->owner_guid;
419
420
			// add new md
421
			foreach ($value as $value_tmp) {
422
				// at this point $value is appended because it was cleared above if needed.
423
				$md_id = _elgg_services()->metadataTable->create($this->guid, $name, $value_tmp, $value_type,
424
						$owner_guid, null, true);
425
				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...
426
					return false;
427
				}
428
			}
429
430
			return true;
431
		} else {
432
			// unsaved entity. store in temp array
433
434
			// returning single entries instead of an array of 1 element is decided in
435
			// getMetaData(), just like pulling from the db.
436
437
			if ($owner_guid != 0) {
438
				$msg = "owner guid cannot be used in ElggEntity::setMetadata() until entity is saved.";
439
				throw new \InvalidArgumentException($msg);
440
			}
441
442 98
			// if overwrite, delete first
443
			if (!$multiple || !isset($this->temp_metadata[$name])) {
444
				$this->temp_metadata[$name] = [];
445
			}
446
447
			// add new md
448 98
			$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
449 98
			return true;
450
		}
451
	}
452
453 98
	/**
454 98
	 * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
455
	 * If you pass a name, only metadata matching that name will be deleted.
456
	 *
457
	 * @warning Calling this with no $name will clear all metadata on the entity.
458
	 *
459
	 * @param null|string $name The name of the metadata to remove.
460
	 * @return bool
461
	 * @since 1.8
462
	 */
463
	public function deleteMetadata($name = null) {
464
465
		if (!$this->guid) {
466
			return false;
467
		}
468 58
469
		$options = [
470 58
			'guid' => $this->guid,
471 15
			'limit' => 0
472
		];
473
		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...
474
			$options['metadata_name'] = $name;
475 43
		}
476 43
477
		return elgg_delete_metadata($options);
478 43
	}
479 42
480
	/**
481
	 * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
482 43
	 * If you pass a name, only metadata matching that name will be deleted.
483
	 *
484
	 * @param null|string $name The name of metadata to delete.
485
	 * @return bool
486
	 * @since 1.8
487
	 */
488 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...
489
		// access is turned off for this because they might
490
		// no longer have access to an entity they created metadata on.
491
		$ia = elgg_set_ignore_access(true);
492
		$options = [
493 1
			'metadata_owner_guid' => $this->guid,
494
			'limit' => 0
495
		];
496 1
		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...
497
			$options['metadata_name'] = $name;
498 1
		}
499 1
500
		$r = elgg_delete_metadata($options);
501 1
		elgg_set_ignore_access($ia);
502
		return $r;
503
	}
504
505 1
	/**
506 1
	 * Disables metadata for this entity, optionally based on name.
507 1
	 *
508
	 * @param string $name An options name of metadata to disable.
509
	 * @return bool
510
	 * @since 1.8
511
	 */
512 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...
513
		$options = [
514
			'guid' => $this->guid,
515
			'limit' => 0
516
		];
517 1
		if ($name) {
518
			$options['metadata_name'] = $name;
519 1
		}
520 1
521
		return elgg_disable_metadata($options);
522 1
	}
523
524
	/**
525
	 * Enables metadata for this entity, optionally based on name.
526 1
	 *
527
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
528
	 *
529
	 * @param string $name An options name of metadata to enable.
530
	 * @return bool
531
	 * @since 1.8
532
	 */
533 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...
534
		$options = [
535
			'guid' => $this->guid,
536
			'limit' => 0
537
		];
538
		if ($name) {
539
			$options['metadata_name'] = $name;
540
		}
541
542
		return elgg_enable_metadata($options);
543
	}
544
545
	/**
546
	 * Get a piece of volatile (non-persisted) data on this entity.
547
	 *
548
	 * @param string $name The name of the volatile data
549
	 *
550
	 * @return mixed The value or null if not found.
551
	 */
552
	public function getVolatileData($name) {
553
		return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
554
	}
555
556
	/**
557 1
	 * Set a piece of volatile (non-persisted) data on this entity
558 1
	 *
559
	 * @param string $name  Name
560
	 * @param mixed  $value Value
561
	 *
562
	 * @return void
563
	 */
564
	public function setVolatileData($name, $value) {
565
		$this->volatile[$name] = $value;
566
	}
567
568
	/**
569 8
	 * Cache the entity in a persisted cache
570 8
	 *
571 8
	 * @param ElggSharedMemoryCache $cache       Memcache or null cache
572
	 * @param int                   $last_action Last action time
573
	 *
574
	 * @return void
575
	 * @access private
576
	 * @internal
577
	 */
578
	public function storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action = 0) {
579
		$tmp = $this->volatile;
580
581
		// don't store volatile data
582
		$this->volatile = [];
583 57
		if ($last_action) {
584 57
			$this->attributes['last_action'] = (int) $last_action;
585
		}
586
		$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...
587 57
588 57
		$this->volatile = $tmp;
589
	}
590
591 57
	/**
592
	 * Remove all relationships to and from this entity.
593 57
	 * If you pass a relationship name, only relationships matching that name
594 57
	 * will be deleted.
595
	 *
596
	 * @warning Calling this with no $relationship will clear all relationships
597
	 * for this entity.
598
	 *
599
	 * @param null|string $relationship The name of the relationship to remove.
600
	 * @return bool
601
	 * @see \ElggEntity::addRelationship()
602
	 * @see \ElggEntity::removeRelationship()
603
	 */
604
	public function deleteRelationships($relationship = null) {
605
		$relationship = (string) $relationship;
606
		$result = remove_entity_relationships($this->getGUID(), $relationship);
607
		return $result && remove_entity_relationships($this->getGUID(), $relationship, true);
608
	}
609 1
610 1
	/**
611 1
	 * Add a relationship between this an another entity.
612 1
	 *
613
	 * @tip Read the relationship like "This entity is a $relationship of $guid_two."
614
	 *
615
	 * @param int    $guid_two     GUID of the target entity of the relationship.
616
	 * @param string $relationship The type of relationship.
617
	 *
618
	 * @return bool
619
	 * @see \ElggEntity::removeRelationship()
620
	 * @see \ElggEntity::deleteRelationships()
621
	 */
622
	public function addRelationship($guid_two, $relationship) {
623
		return add_entity_relationship($this->getGUID(), $relationship, $guid_two);
624
	}
625
626
	/**
627 1
	 * Remove a relationship
628 1
	 *
629
	 * @param int    $guid_two     GUID of the target entity of the relationship.
630
	 * @param string $relationship The type of relationship.
631
	 *
632
	 * @return bool
633
	 * @see \ElggEntity::addRelationship()
634
	 * @see \ElggEntity::deleteRelationships()
635
	 */
636
	public function removeRelationship($guid_two, $relationship) {
637
		return remove_entity_relationship($this->getGUID(), $relationship, $guid_two);
638
	}
639
640
	/**
641 1
	 * Adds a private setting to this entity.
642 1
	 *
643
	 * Private settings are similar to metadata but will not
644
	 * be searched and there are fewer helper functions for them.
645
	 *
646
	 * @param string $name  Name of private setting
647
	 * @param mixed  $value Value of private setting
648
	 *
649
	 * @return bool
650
	 */
651
	public function setPrivateSetting($name, $value) {
652
		if ((int) $this->guid > 0) {
653
			return set_private_setting($this->getGUID(), $name, $value);
654
		} else {
655
			$this->temp_private_settings[$name] = $value;
656 11
			return true;
657 11
		}
658 5
	}
659
660 11
	/**
661 11
	 * Returns a private setting value
662
	 *
663
	 * @param string $name Name of the private setting
664
	 *
665
	 * @return mixed Null if the setting does not exist
666
	 */
667
	public function getPrivateSetting($name) {
668
		if ((int) ($this->guid) > 0) {
669
			return get_private_setting($this->getGUID(), $name);
670
		} else {
671
			if (isset($this->temp_private_settings[$name])) {
672 11
				return $this->temp_private_settings[$name];
673 11
			}
674 5
		}
675
		return null;
676 11
	}
677 10
678
	/**
679
	 * Removes private setting
680 2
	 *
681
	 * @param string $name Name of the private setting
682
	 *
683
	 * @return bool
684
	 */
685
	public function removePrivateSetting($name) {
686
		return remove_private_setting($this->getGUID(), $name);
687
	}
688
689
	/**
690
	 * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
691
	 * If you pass a name, only annotations matching that name will be deleted.
692
	 *
693
	 * @warning Calling this with no or empty arguments will clear all annotations on the entity.
694
	 *
695
	 * @param null|string $name The annotations name to remove.
696
	 * @return bool
697
	 * @since 1.8
698
	 */
699 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...
700
		$options = [
701
			'guid' => $this->guid,
702
			'limit' => 0
703
		];
704 1
		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...
705
			$options['annotation_name'] = $name;
706 1
		}
707 1
708
		return elgg_delete_annotations($options);
709 1
	}
710
711
	/**
712
	 * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
713 1
	 * If you pass a name, only annotations matching that name will be deleted.
714
	 *
715
	 * @param null|string $name The name of annotations to delete.
716
	 * @return bool
717
	 * @since 1.8
718
	 */
719 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...
720
		// access is turned off for this because they might
721
		// no longer have access to an entity they created annotations on.
722
		$ia = elgg_set_ignore_access(true);
723
		$options = [
724 1
			'annotation_owner_guid' => $this->guid,
725
			'limit' => 0
726
		];
727 1
		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...
728
			$options['annotation_name'] = $name;
729 1
		}
730 1
731
		$r = elgg_delete_annotations($options);
732 1
		elgg_set_ignore_access($ia);
733
		return $r;
734
	}
735
736 1
	/**
737 1
	 * Disables annotations for this entity, optionally based on name.
738 1
	 *
739
	 * @param string $name An options name of annotations to disable.
740
	 * @return bool
741
	 * @since 1.8
742
	 */
743 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...
744
		$options = [
745
			'guid' => $this->guid,
746
			'limit' => 0
747
		];
748 1
		if ($name) {
749
			$options['annotation_name'] = $name;
750 1
		}
751 1
752
		return elgg_disable_annotations($options);
753 1
	}
754
755
	/**
756
	 * Enables annotations for this entity, optionally based on name.
757 1
	 *
758
	 * @warning Before calling this, you must use {@link access_show_hidden_entities()}
759
	 *
760
	 * @param string $name An options name of annotations to enable.
761
	 * @return bool
762
	 * @since 1.8
763
	 */
764 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...
765
		$options = [
766
			'guid' => $this->guid,
767
			'limit' => 0
768
		];
769
		if ($name) {
770
			$options['annotation_name'] = $name;
771
		}
772
773
		return elgg_enable_annotations($options);
774
	}
775
776
	/**
777
	 * Helper function to return annotation calculation results
778
	 *
779
	 * @param string $name        The annotation name.
780
	 * @param string $calculation A valid MySQL function to run its values through
781
	 * @return mixed
782
	 */
783
	private function getAnnotationCalculation($name, $calculation) {
784
		$options = [
785
			'guid' => $this->getGUID(),
786
			'distinct' => false,
787
			'annotation_name' => $name,
788
			'annotation_calculation' => $calculation
789
		];
790
791
		return elgg_get_annotations($options);
792
	}
793
794
	/**
795
	 * Adds an annotation to an entity.
796
	 *
797
	 * @warning By default, annotations are private.
798
	 *
799
	 * @warning Annotating an unsaved entity more than once with the same name
800
	 *          will only save the last annotation.
801
	 *
802
	 * @param string $name       Annotation name
803
	 * @param mixed  $value      Annotation value
804
	 * @param int    $access_id  Access ID
805
	 * @param int    $owner_guid GUID of the annotation owner
806
	 * @param string $vartype    The type of annotation value
807
	 *
808
	 * @return bool|int Returns int if an annotation is saved
809
	 */
810
	public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $vartype = "") {
811
		if ((int) $this->guid > 0) {
812
			return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_guid, $access_id);
813
		} else {
814
			$this->temp_annotations[$name] = $value;
815 84
		}
816 84
		return true;
817 84
	}
818
819
	/**
820
	 * Gets an array of annotations.
821
	 *
822
	 * To retrieve annotations on an unsaved entity, pass array('name' => [annotation name])
823
	 * as the options array.
824
	 *
825
	 * @param array $options Array of options for elgg_get_annotations() except guid.
826
	 *
827
	 * @return array
828
	 * @see elgg_get_annotations()
829
	 */
830
	public function getAnnotations(array $options = []) {
831
		if ($this->guid) {
832
			$options['guid'] = $this->guid;
833
834
			return elgg_get_annotations($options);
835
		} else {
836
			$name = elgg_extract('annotation_name', $options, '');
837
838
			if (isset($this->temp_annotations[$name])) {
839
				return [$this->temp_annotations[$name]];
840
			}
841
		}
842
843
		return [];
844
	}
845
846
	/**
847
	 * Count annotations.
848
	 *
849
	 * @param string $name The type of annotation.
850
	 *
851
	 * @return int
852
	 */
853
	public function countAnnotations($name = "") {
854
		return $this->getAnnotationCalculation($name, 'count');
855
	}
856
857
	/**
858
	 * Get the average of an integer type annotation.
859
	 *
860
	 * @param string $name Annotation name
861
	 *
862
	 * @return int
863
	 */
864
	public function getAnnotationsAvg($name) {
865
		return $this->getAnnotationCalculation($name, 'avg');
866
	}
867
868
	/**
869
	 * Get the sum of integer type annotations of a given name.
870
	 *
871
	 * @param string $name Annotation name
872
	 *
873
	 * @return int
874
	 */
875
	public function getAnnotationsSum($name) {
876
		return $this->getAnnotationCalculation($name, 'sum');
877
	}
878
879
	/**
880
	 * Get the minimum of integer type annotations of given name.
881
	 *
882
	 * @param string $name Annotation name
883
	 *
884
	 * @return int
885
	 */
886
	public function getAnnotationsMin($name) {
887
		return $this->getAnnotationCalculation($name, 'min');
888
	}
889
890
	/**
891
	 * Get the maximum of integer type annotations of a given name.
892
	 *
893
	 * @param string $name Annotation name
894
	 *
895
	 * @return int
896
	 */
897
	public function getAnnotationsMax($name) {
898
		return $this->getAnnotationCalculation($name, 'max');
899
	}
900
901
	/**
902
	 * Count the number of comments attached to this entity.
903
	 *
904
	 * @return int Number of comments
905
	 * @since 1.8.0
906
	 */
907
	public function countComments() {
908
		$params = ['entity' => $this];
909
		$num = _elgg_services()->hooks->trigger('comments:count', $this->getType(), $params);
910
911
		if (is_int($num)) {
912
			return $num;
913
		} else {
914
			return elgg_get_entities([
915
				'type' => 'object',
916
				'subtype' => 'comment',
917
				'container_guid' => $this->getGUID(),
918
				'count' => true,
919
				'distinct' => false,
920
			]);
921
		}
922
	}
923
924
	/**
925
	 * Gets an array of entities with a relationship to this entity.
926
	 *
927
	 * @param array $options Options array. See elgg_get_entities_from_relationship()
928
	 *                       for a list of options. 'relationship_guid' is set to
929
	 *                       this entity.
930
	 *
931
	 * @return array|false An array of entities or false on failure
932
	 * @see elgg_get_entities_from_relationship()
933
	 */
934
	public function getEntitiesFromRelationship(array $options = []) {
935
		$options['relationship_guid'] = $this->guid;
936
		return elgg_get_entities_from_relationship($options);
937
	}
938
939
	/**
940
	 * Gets the number of entities from a specific relationship type
941
	 *
942
	 * @param string $relationship         Relationship type (eg "friends")
943
	 * @param bool   $inverse_relationship Invert relationship
944
	 *
945
	 * @return int|false The number of entities or false on failure
946
	 */
947
	public function countEntitiesFromRelationship($relationship, $inverse_relationship = false) {
948
		return elgg_get_entities_from_relationship([
949
			'relationship' => $relationship,
950
			'relationship_guid' => $this->getGUID(),
951
			'inverse_relationship' => $inverse_relationship,
952
			'count' => true
953
		]);
954
	}
955
956
	/**
957
	 * Can a user edit this entity?
958
	 *
959
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
960
	 *
961
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
962
	 *
963
	 * @return bool Whether this entity is editable by the given user.
964
	 * @see elgg_set_ignore_access()
965
	 */
966
	public function canEdit($user_guid = 0) {
967
		return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
968
	}
969
970
	/**
971 22
	 * Can a user delete this entity?
972 22
	 *
973
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
974
	 *
975
	 * @param int $user_guid The user GUID, optionally (default: logged in user)
976
	 *
977
	 * @return bool Whether this entity is deletable by the given user.
978
	 * @since 1.11
979
	 * @see elgg_set_ignore_access()
980
	 */
981
	public function canDelete($user_guid = 0) {
982
		return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
983
	}
984
985
	/**
986 4
	 * Can a user edit metadata on this entity?
987 4
	 *
988
	 * If no specific metadata is passed, it returns whether the user can
989
	 * edit any metadata on the entity.
990
	 *
991
	 * @tip Can be overridden by by registering for the permissions_check:metadata
992
	 * plugin hook.
993
	 *
994
	 * @param \ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
995
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
996
	 *
997
	 * @return bool
998
	 * @see elgg_set_ignore_access()
999
	 */
1000
	public function canEditMetadata($metadata = null, $user_guid = 0) {
1001
		return _elgg_services()->userCapabilities->canEditMetadata($this, $user_guid, $metadata);
1002
	}
1003
1004
	/**
1005 8
	 * Can a user add an entity to this container
1006 8
	 *
1007
	 * @param int    $user_guid The GUID of the user creating the entity (0 for logged in user).
1008
	 * @param string $type      The type of entity we're looking to write
1009
	 * @param string $subtype   The subtype of the entity we're looking to write
1010
	 *
1011
	 * @return bool
1012
	 * @see elgg_set_ignore_access()
1013
	 */
1014
	public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') {
1015
		return _elgg_services()->userCapabilities->canWriteToContainer($this, $user_guid, $type, $subtype);
1016
	}
1017
1018
	/**
1019 5
	 * Can a user comment on an entity?
1020 5
	 *
1021
	 * @tip Can be overridden by registering for the permissions_check:comment,
1022
	 * <entity type> plugin hook.
1023
	 *
1024
	 * @param int  $user_guid User guid (default is logged in user)
1025
	 * @param bool $default   Default permission
1026
	 * @return bool
1027
	 */
1028
	public function canComment($user_guid = 0, $default = null) {
1029
		return _elgg_services()->userCapabilities->canComment($this, $user_guid, $default);
1030
	}
1031
1032
	/**
1033 5
	 * Can a user annotate an entity?
1034 5
	 *
1035
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
1036
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
1037
	 *
1038
	 * @tip If you want logged out users to annotate an object, do not call
1039
	 * canAnnotate(). It's easier than using the plugin hook.
1040
	 *
1041
	 * @param int    $user_guid       User guid (default is logged in user)
1042
	 * @param string $annotation_name The name of the annotation (default is unspecified)
1043
	 *
1044
	 * @return bool
1045
	 */
1046
	public function canAnnotate($user_guid = 0, $annotation_name = '') {
1047
		return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
1048
	}
1049
1050
	/**
1051 8
	 * Returns the access_id.
1052 8
	 *
1053
	 * @return int The access ID
1054
	 */
1055
	public function getAccessID() {
1056
		return $this->access_id;
1057
	}
1058
1059
	/**
1060 1
	 * Returns the guid.
1061 1
	 *
1062
	 * @return int|null GUID
1063
	 */
1064
	public function getGUID() {
1065
		return $this->guid;
1066
	}
1067
1068
	/**
1069 155
	 * Returns the entity type
1070 155
	 *
1071
	 * @return string The entity type
1072
	 */
1073
	public function getType() {
1074
		// this is just for the PHPUnit mocking framework
1075
		return $this->type;
1076
	}
1077
1078 1
	/**
1079
	 * Get the entity subtype
1080 1
	 *
1081
	 * @return string The entity subtype
1082
	 */
1083
	public function getSubtype() {
1084
		// If this object hasn't been saved, then return the subtype string.
1085
		if ($this->attributes['guid']) {
1086
			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 1086 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...
1087
		}
1088 88
		return $this->attributes['subtype'];
1089
	}
1090 88
1091 83
	/**
1092
	 * Get the guid of the entity's owner.
1093 8
	 *
1094
	 * @return int The owner GUID
1095
	 */
1096
	public function getOwnerGUID() {
1097
		return (int) $this->owner_guid;
1098
	}
1099
1100
	/**
1101 79
	 * Gets the \ElggEntity that owns this entity.
1102 79
	 *
1103
	 * @return \ElggEntity The owning entity
1104
	 */
1105
	public function getOwnerEntity() {
1106
		return get_entity($this->owner_guid);
1107
	}
1108
1109
	/**
1110 1
	 * Set the container for this object.
1111 1
	 *
1112
	 * @param int $container_guid The ID of the container.
1113
	 *
1114
	 * @return bool
1115
	 */
1116
	public function setContainerGUID($container_guid) {
1117
		return $this->container_guid = (int) $container_guid;
1118
	}
1119
1120
	/**
1121
	 * Gets the container GUID for this entity.
1122
	 *
1123
	 * @return int
1124
	 */
1125
	public function getContainerGUID() {
1126
		return (int) $this->container_guid;
1127
	}
1128
1129
	/**
1130 12
	 * Get the container entity for this object.
1131 12
	 *
1132
	 * @return \ElggEntity
1133
	 * @since 1.8.0
1134
	 */
1135
	public function getContainerEntity() {
1136
		return get_entity($this->getContainerGUID());
1137
	}
1138
1139
	/**
1140 8
	 * Returns the UNIX epoch time that this entity was last updated
1141 8
	 *
1142
	 * @return int UNIX epoch time
1143
	 */
1144
	public function getTimeUpdated() {
1145
		return $this->time_updated;
1146
	}
1147
1148
	/**
1149 3
	 * Gets the URL for this entity.
1150 3
	 *
1151
	 * Plugins can register for the 'entity:url', <type> plugin hook to
1152
	 * customize the url for an entity.
1153
	 *
1154
	 * @return string The URL of the entity
1155
	 */
1156
	public function getURL() {
1157
		$url = _elgg_services()->hooks->trigger('entity:url', $this->getType(), ['entity' => $this]);
1158
		
1159
		if ($url === null || $url === '' || $url === false) {
1160
			return '';
1161 11
		}
1162 11
1163
		return elgg_normalize_url($url);
1164 11
	}
1165 11
1166
	/**
1167
	 * Saves icons using an uploaded file as the source.
1168
	 *
1169
	 * @param string $input_name Form input name
1170
	 * @param string $type       The name of the icon. e.g., 'icon', 'cover_photo'
1171
	 * @param array  $coords     An array of cropping coordinates x1, y1, x2, y2
1172
	 * @return bool
1173
	 */
1174
	public function saveIconFromUploadedFile($input_name, $type = 'icon', array $coords = []) {
1175
		return _elgg_services()->iconService->saveIconFromUploadedFile($this, $input_name, $type, $coords);
1176
	}
1177
1178
	/**
1179
	 * Saves icons using a local file as the source.
1180
	 *
1181
	 * @param string $filename The full path to the local file
1182
	 * @param string $type     The name of the icon. e.g., 'icon', 'cover_photo'
1183
	 * @param array  $coords   An array of cropping coordinates x1, y1, x2, y2
1184
	 * @return bool
1185
	 */
1186
	public function saveIconFromLocalFile($filename, $type = 'icon', array $coords = []) {
1187
		return _elgg_services()->iconService->saveIconFromLocalFile($this, $filename, $type, $coords);
1188
	}
1189
1190
	/**
1191
	 * Saves icons using a file located in the data store as the source.
1192
	 *
1193
	 * @param string $file   An ElggFile instance
1194
	 * @param string $type   The name of the icon. e.g., 'icon', 'cover_photo'
1195
	 * @param array  $coords An array of cropping coordinates x1, y1, x2, y2
1196
	 * @return bool
1197
	 */
1198
	public function saveIconFromElggFile(\ElggFile $file, $type = 'icon', array $coords = []) {
1199
		return _elgg_services()->iconService->saveIconFromElggFile($this, $file, $type, $coords);
1200
	}
1201
	
1202
	/**
1203
	 * Returns entity icon as an ElggIcon object
1204
	 * The icon file may or may not exist on filestore
1205
	 *
1206
	 * @param string $size Size of the icon
1207
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1208
	 * @return \ElggIcon
1209
	 */
1210
	public function getIcon($size, $type = 'icon') {
1211
		return _elgg_services()->iconService->getIcon($this, $size, $type);
1212
	}
1213
1214
	/**
1215 1
	 * Removes all icon files and metadata for the passed type of icon.
1216 1
	 *
1217
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1218
	 * @return bool
1219
	 */
1220
	public function deleteIcon($type = 'icon') {
1221
		return _elgg_services()->iconService->deleteIcon($this, $type);
1222
	}
1223
	
1224
	/**
1225
	 * Returns the timestamp of when the icon was changed.
1226
	 *
1227
	 * @param string $size The size of the icon
1228
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1229
	 *
1230
	 * @return int|null A unix timestamp of when the icon was last changed, or null if not set.
1231
	 */
1232
	public function getIconLastChange($size, $type = 'icon') {
1233
		return _elgg_services()->iconService->getIconLastChange($this, $size, $type);
1234
	}
1235
	
1236
	/**
1237
	 * Returns if the entity has an icon of the passed type.
1238
	 *
1239
	 * @param string $size The size of the icon
1240
	 * @param string $type The name of the icon. e.g., 'icon', 'cover_photo'
1241
	 * @return bool
1242
	 */
1243
	public function hasIcon($size, $type = 'icon') {
1244
		return _elgg_services()->iconService->hasIcon($this, $size, $type);
1245
	}
1246
1247
	/**
1248
	 * Get the URL for this entity's icon
1249
	 *
1250
	 * Plugins can register for the 'entity:icon:url', <type> plugin hook
1251
	 * to customize the icon for an entity.
1252
	 *
1253
	 * @param mixed $params A string defining the size of the icon (e.g. tiny, small, medium, large)
1254
	 *                      or an array of parameters including 'size'
1255
	 * @return string The URL
1256
	 * @since 1.8.0
1257
	 */
1258
	public function getIconURL($params = []) {
1259
		return _elgg_services()->iconService->getIconURL($this, $params);
1260
	}
1261
1262
	/**
1263
	 * Save an entity.
1264
	 *
1265
	 * @return bool|int
1266
	 * @throws InvalidParameterException
1267
	 * @throws IOException
1268
	 */
1269
	public function save() {
1270
		$guid = $this->guid;
1271
		if ($guid > 0) {
1272
			$guid = $this->update();
1273
		} else {
1274 8
			$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 1289 which is incompatible with the return type declared by the abstract method ElggData::save of type boolean.
Loading history...
1275 8
			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...
1276 8
				// plugins that return false to event don't need to override the access system
1277 2
				$ia = elgg_set_ignore_access(true);
1278
				$this->delete();
1279 6
				elgg_set_ignore_access($ia);
1280 6
				return false;
1281
			}
1282
		}
1283
1284
		if ($guid) {
1285
			_elgg_services()->entityCache->set($this);
1286
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1287
		}
1288
1289 8
		return $guid;
1290 7
	}
1291 7
1292
	/**
1293
	 * Create a new entry in the entities table.
1294 8
	 *
1295
	 * Saves the base information in the entities table for the entity.  Saving
1296
	 * the type-specific information is handled in the calling class method.
1297
	 *
1298
	 * @warning Entities must have an entry in both the entities table and their type table
1299
	 * or they will throw an exception when loaded.
1300
	 *
1301
	 * @return int The new entity's GUID
1302
	 * @throws InvalidParameterException If the entity's type has not been set.
1303
	 * @throws IOException If the new row fails to write to the DB.
1304
	 */
1305
	protected function create() {
1306
1307
		$type = $this->attributes['type'];
1308
		if (!in_array($type, \Elgg\Config::getEntityTypes())) {
1309
			throw new \InvalidParameterException('Entity type must be one of the allowed types: '
1310 6
					. implode(', ', \Elgg\Config::getEntityTypes()));
1311
		}
1312 6
		
1313 6
		$subtype = $this->attributes['subtype'];
1314 6
		$subtype_id = add_subtype($type, $subtype);
1315
		$owner_guid = (int) $this->attributes['owner_guid'];
1316
		$access_id = (int) $this->attributes['access_id'];
1317
		$now = $this->getCurrentTime()->getTimestamp();
1318
		$time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1319 6
		
1320 6
		$container_guid = $this->attributes['container_guid'];
1321 6
		if ($container_guid == 0) {
1322 6
			$container_guid = $owner_guid;
1323 6
			$this->attributes['container_guid'] = $container_guid;
1324 6
		}
1325
		$container_guid = (int) $container_guid;
1326 6
1327 6
		if ($access_id == ACCESS_DEFAULT) {
1328 5
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
1329 5
		}
1330
1331 6
		$user_guid = elgg_get_logged_in_user_guid();
1332
1333 6
		// If given an owner, verify it can be loaded
1334 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...
1335
			$owner = $this->getOwnerEntity();
1336
			if (!$owner) {
1337 6
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1338
					. " owner $owner_guid could not be loaded.");
1339
				return false;
1340 6
			}
1341 1
1342 1
			// If different owner than logged in, verify can write to container.
1343
1344
			if ($user_guid != $owner_guid && !$owner->canWriteToContainer(0, $type, $subtype)) {
1345
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype) with owner"
1346
					. " $owner_guid, but the user wasn't permitted to write to the owner's container.");
1347
				return false;
1348
			}
1349
		}
1350 1
1351
		// If given a container, verify it can be loaded and that the current user can write to it
1352 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...
1353
			$container = $this->getContainerEntity();
1354
			if (!$container) {
1355
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but the given"
1356
					. " container $container_guid could not be loaded.");
1357
				return false;
1358 6
			}
1359 1
1360 1
			if (!$container->canWriteToContainer(0, $type, $subtype)) {
1361
				_elgg_services()->logger->error("User $user_guid tried to create a ($type, $subtype), but was not"
1362
					. " permitted to write to container $container_guid.");
1363
				return false;
1364
			}
1365
		}
1366 1
1367
		// Create primary table row
1368
		$guid = _elgg_services()->entityTable->insertRow((object) [
1369
			'type' => $type,
1370
			'subtype_id' => $subtype_id,
1371
			'owner_guid' => $owner_guid,
1372
			'container_guid' => $container_guid,
1373
			'access_id' => $access_id,
1374 6
			'time_created' => $time_created,
1375 6
			'time_updated' => $now,
1376 6
			'last_action' => $now,
1377 6
		], $this->attributes);
1378 6
1379 6
		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...
1380 6
			throw new \IOException("Unable to save new object's base entity information!");
1381 6
		}
1382 6
1383 6
		// We are writing this new entity to cache to make sure subsequent calls
1384
		// to get_entity() load the entity from cache and not from the DB. This
1385 6
		// MUST come before the metadata and annotation writes below!
1386
		_elgg_services()->entityCache->set($this);
1387
	
1388
		// for BC with 1.8, ->subtype always returns ID, ->getSubtype() the string
1389
		$this->attributes['subtype'] = (int) $subtype_id;
1390
		$this->attributes['guid'] = (int) $guid;
1391
		$this->attributes['time_created'] = (int) $time_created;
1392 6
		$this->attributes['time_updated'] = (int) $now;
1393
		$this->attributes['last_action'] = (int) $now;
1394
		$this->attributes['container_guid'] = (int) $container_guid;
1395 6
1396 6
		// Create secondary table row
1397 6
		$attrs = $this->getSecondaryTableColumns();
1398 6
1399 6
		$column_names = implode(', ', $attrs);
1400 6
		$values = implode(', ', array_map(function ($attr) {
1401
			return ":$attr";
1402
		}, $attrs));
1403 6
1404
		$params = [
1405 6
			':guid' => $guid,
1406
		];
1407 6 View Code Duplication
		foreach ($attrs as $attr) {
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...
1408 6
			$params[":$attr"] = ($attr === 'url') ? '' : (string) $this->attributes[$attr];
1409
		}
1410
1411 6
		$db = $this->getDatabase();
1412
		$query = "
1413 6
			INSERT INTO {$db->prefix}{$this->type}s_entity
1414 6
			(guid, $column_names) VALUES (:guid, $values)
1415
		";
1416
1417 6
		if ($db->insertData($query, $params) === false) {
1418
			// Uh oh, couldn't save secondary
1419 6
			$query = "
1420 6
				DELETE FROM {$db->prefix}entities
1421
				WHERE guid = :guid
1422
			";
1423 6
			$params = [
1424
				':guid' => $guid,
1425
			];
1426
			$db->deleteData($query, $params);
1427
1428
			_elgg_services()->entityCache->remove($guid);
1429
1430
			throw new \IOException("Unable to save new object's secondary entity information!");
1431
		}
1432
1433
		// Save any unsaved metadata
1434
		if (sizeof($this->temp_metadata) > 0) {
1435
			foreach ($this->temp_metadata as $name => $value) {
1436
				$this->$name = $value;
1437
			}
1438
1439
			$this->temp_metadata = [];
1440 6
		}
1441
1442
		// Save any unsaved annotations.
1443
		if (sizeof($this->temp_annotations) > 0) {
1444
			foreach ($this->temp_annotations as $name => $value) {
1445
				$this->annotate($name, $value);
1446
			}
1447
1448
			$this->temp_annotations = [];
1449 6
		}
1450
1451
		// Save any unsaved private settings.
1452
		if (sizeof($this->temp_private_settings) > 0) {
1453
			foreach ($this->temp_private_settings as $name => $value) {
1454
				$this->setPrivateSetting($name, $value);
1455
			}
1456
1457
			$this->temp_private_settings = [];
1458 6
		}
1459 5
		
1460 5
		return $guid;
1461
	}
1462
1463 5
	/**
1464
	 * Update the entity in the database.
1465
	 *
1466 6
	 * @return bool Whether the update was successful.
1467
	 *
1468
	 * @throws InvalidParameterException
1469
	 */
1470
	protected function update() {
1471
		
1472
		_elgg_services()->boot->invalidateCache();
1473
1474
		if (!$this->canEdit()) {
1475
			return false;
1476 2
		}
1477
1478 2
		// give old update event a chance to stop the update
1479
		if (!_elgg_services()->hooks->getEvents()->trigger('update', $this->type, $this)) {
1480 2
			return false;
1481 1
		}
1482
1483
		// See #6225. We copy these after the update event in case a handler changed one of them.
1484
		$guid = (int) $this->guid;
1485 1
		$owner_guid = (int) $this->owner_guid;
1486
		$access_id = (int) $this->access_id;
1487
		$container_guid = (int) $this->container_guid;
1488
		$time_created = (int) $this->time_created;
1489
		$time = $this->getCurrentTime()->getTimestamp();
1490 1
1491 1
		if ($access_id == ACCESS_DEFAULT) {
1492 1
			throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
1493 1
		}
1494 1
1495 1
		// Update primary table
1496
		$ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1497 1
			'owner_guid' => $owner_guid,
1498
			'container_guid' => $container_guid,
1499
			'access_id' => $access_id,
1500
			'time_created' => $time_created,
1501
			'time_updated' => $time,
1502 1
			'guid' => $guid,
1503 1
		]);
1504 1
		if ($ret === false) {
1505 1
			return false;
1506 1
		}
1507 1
1508 1
		$this->attributes['time_updated'] = $time;
1509
1510 1
		// Update secondary table
1511
		$attrs = $this->getSecondaryTableColumns();
1512
1513
		$sets = array_map(function ($attr) {
1514 1
			return "$attr = :$attr";
1515
		}, $attrs);
1516
		$sets = implode(', ', $sets);
1517 1
1518 View Code Duplication
		foreach ($attrs as $attr) {
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...
1519 1
			$params[":$attr"] = ($attr === 'url') ? '' : (string) $this->attributes[$attr];
1520 1
		}
1521 1
		$params[':guid'] = $this->guid;
0 ignored issues
show
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1522 1
1523
		$db = $this->getDatabase();
1524 1
		$query = "
1525 1
			UPDATE {$db->prefix}{$this->type}s_entity
1526
			SET $sets
1527 1
			WHERE guid = :guid
1528
		";
1529 1
1530
		if ($db->updateData($query, false, $params) === false) {
1531 1
			return false;
1532 1
		}
1533
1534
		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...
1535
1536 1
		// TODO(evan): Move this to \ElggObject?
1537
		if ($this instanceof \ElggObject) {
1538
			update_river_access_by_object($guid, $access_id);
1539
		}
1540 1
1541
		$this->orig_attributes = [];
1542
1543 1
		// Handle cases where there was no error BUT no rows were updated!
1544 1
		return true;
1545
	}
1546
1547 1
	/**
1548
	 * Loads attributes from the entities table into the object.
1549
	 *
1550 1
	 * @param \stdClass $row Object of properties from database row(s)
1551
	 *
1552
	 * @return bool
1553
	 */
1554
	protected function load(\stdClass $row) {
1555
		$type = $this->type;
1556
1557
		$attr_loader = new \Elgg\AttributeLoader(get_class($this), $type, $this->attributes);
1558
		if ($type === 'user' || $this instanceof ElggPlugin) {
1559
			$attr_loader->requires_access_control = false;
1560 261
		}
1561 261
		$attr_loader->secondary_loader = "get_{$type}_entity_as_row";
1562
1563 261
		$attrs = $attr_loader->getRequiredAttributes($row);
1564 261
		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...
1565 220
			return false;
1566
		}
1567 261
1568
		$this->attributes = $attrs;
1569 261
1570 261
		foreach ($attr_loader->getAdditionalSelectValues() as $name => $value) {
1571
			$this->setVolatileData("select:$name", $value);
1572
		}
1573
1574 261
		_elgg_services()->entityCache->set($this);
1575
1576 261
		return true;
1577 7
	}
1578
1579
	/**
1580 261
	 * Get the added columns (besides GUID) stored in the secondary table
1581
	 *
1582 261
	 * @return string[]
1583
	 * @throws \InvalidArgumentException
1584
	 */
1585
	private function getSecondaryTableColumns() {
1586
		// Note: the title or name column must come first. See getDisplayName().
1587
		if ($this instanceof ElggObject) {
1588
			return ['title', 'description'];
1589
		}
1590
		if ($this instanceof ElggUser) {
1591 14
			return ['name', 'username', 'password_hash', 'email', 'language'];
1592
		}
1593 14
		if ($this instanceof ElggGroup) {
1594 11
			return ['name', 'description'];
1595
		}
1596 4
		if ($this instanceof ElggSite) {
1597 4
			return ['name', 'description', 'url'];
1598
		}
1599 2
		throw new \InvalidArgumentException("Not a recognized type: " . get_class($this));
1600 1
	}
1601
1602 1
	/**
1603 1
	 * Get default values for the attributes not defined in \ElggEntity::initializeAttributes
1604
	 *
1605
	 * @param string $type Entity type
1606
	 *
1607
	 * @return array
1608
	 * @access private
1609
	 */
1610
	public static function getExtraAttributeDefaults($type) {
1611
		switch ($type) {
1612
			case 'object':
1613
				return [
1614
					'title' => null,
1615
					'description' => null,
1616 329
				];
1617
			case 'user':
1618 329
				return [
1619
					'name' => null,
1620 273
					'username' => null,
1621
					'password_hash' => null,
1622
					'email' => null,
1623 269
					'language' => null,
1624
					'banned' => "no",
1625 223
					'admin' => 'no',
1626
					'prev_last_action' => null,
1627
					'last_login' => null,
1628
					'prev_last_login' => null,
1629
				];
1630
			case 'group':
1631
				return [
1632
					'name' => null,
1633
					'description' => null,
1634
				];
1635
			case 'site':
1636 261
				return [
1637
					'name' => null,
1638 87
					'description' => null,
1639
					'url' => null,
1640
				];
1641 260
		}
1642
		throw new \InvalidArgumentException("Not a recognized type: $type");
1643 260
	}
1644
	
1645
	/**
1646
	 * Load new data from database into existing entity. Overwrites data but
1647
	 * does not change values not included in the latest data.
1648
	 *
1649
	 * @internal This is used when the same entity is selected twice during a
1650
	 * request in case different select clauses were used to load different data
1651
	 * into volatile data.
1652
	 *
1653
	 * @param \stdClass $row DB row with new entity data
1654
	 * @return bool
1655
	 * @access private
1656
	 */
1657
	public function refresh(\stdClass $row) {
1658
		if ($row instanceof \stdClass) {
1659
			return $this->load($row);
1660
		}
1661
		return false;
1662
	}
1663
1664
	/**
1665
	 * Disable this entity.
1666
	 *
1667
	 * Disabled entities are not returned by getter functions.
1668
	 * To enable an entity, use {@link \ElggEntity::enable()}.
1669
	 *
1670
	 * Recursively disabling an entity will disable all entities
1671
	 * owned or contained by the parent entity.
1672
	 *
1673
	 * You can ignore the disabled field by using {@link access_show_hidden_entities()}.
1674
	 *
1675
	 * @note Internal: Disabling an entity sets the 'enabled' column to 'no'.
1676
	 *
1677
	 * @param string $reason    Optional reason
1678
	 * @param bool   $recursive Recursively disable all contained entities?
1679
	 *
1680
	 * @return bool
1681
	 * @see \ElggEntity::enable()
1682
	 */
1683
	public function disable($reason = "", $recursive = true) {
1684
		if (!$this->guid) {
1685
			return false;
1686
		}
1687
		
1688
		if (!_elgg_services()->hooks->getEvents()->trigger('disable', $this->type, $this)) {
1689 2
			return false;
1690 2
		}
1691 1
		
1692
		if (!$this->canEdit()) {
1693
			return false;
1694 1
		}
1695
1696
		if ($this instanceof ElggUser && $this->banned === 'no') {
1697
			// temporarily ban to prevent using the site during disable
1698 1
			_elgg_services()->usersTable->markBanned($this->guid, true);
1699
			$unban_after = true;
1700
		} else {
1701
			$unban_after = false;
1702 1
		}
1703
1704
		if ($reason) {
1705
			$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...
1706
		}
1707 1
1708
		$dbprefix = _elgg_config()->dbprefix;
1709
		
1710 1
		$guid = (int) $this->guid;
1711
		
1712
		if ($recursive) {
1713
			// Only disable enabled subentities
1714 1
			$hidden = access_get_show_hidden_status();
1715
			access_show_hidden_entities(false);
1716 1
1717
			$ia = elgg_set_ignore_access(true);
1718 1
1719
			$base_options = [
1720 1
				'wheres' => [
1721 1
					"e.guid != $guid",
1722
				],
1723 1
				'limit' => false,
1724
			];
1725
			
1726 1
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1727 1
				$options = $base_options;
1728
				$options[$db_column] = $guid;
1729
				
1730
				$subentities = new \ElggBatch('elgg_get_entities', $options);
1731
				$subentities->setIncrementOffset(false);
1732 1
				
1733 1
				foreach ($subentities as $subentity) {
1734 1
					/* @var $subentity \ElggEntity */
1735
					if (!$subentity->isEnabled()) {
1736 1
						continue;
1737 1
					}
1738
					add_entity_relationship($subentity->guid, 'disabled_with', $guid);
1739 1
					$subentity->disable($reason);
1740
				}
1741
			}
1742
1743
			access_show_hidden_entities($hidden);
1744
			elgg_set_ignore_access($ia);
1745 1
		}
1746
1747
		$this->disableMetadata();
1748
		$this->disableAnnotations();
1749 1
1750 1
		_elgg_services()->entityCache->remove($guid);
1751
		_elgg_get_memcache('new_entity_cache')->delete($guid);
1752
		
1753 1
		$sql = "
1754 1
			UPDATE {$dbprefix}entities
1755
			SET enabled = 'no'
1756 1
			WHERE guid = :guid
1757 1
		";
1758
		$params = [
1759
			':guid' => $guid,
1760 1
		];
1761
		$disabled = $this->getDatabase()->updateData($sql, false, $params);
1762
1763
		if ($unban_after) {
1764
			_elgg_services()->usersTable->markBanned($this->guid, false);
1765 1
		}
1766
1767 1 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...
1768
			$this->attributes['enabled'] = 'no';
1769 1
			_elgg_services()->hooks->getEvents()->trigger('disable:after', $this->type, $this);
1770
		}
1771
1772
		return (bool) $disabled;
1773 1
	}
1774 1
1775 1
	/**
1776
	 * Enable the entity
1777
	 *
1778 1
	 * @warning Disabled entities can't be loaded unless
1779
	 * {@link access_show_hidden_entities(true)} has been called.
1780
	 *
1781
	 * @param bool $recursive Recursively enable all entities disabled with the entity?
1782
	 * @see access_show_hiden_entities()
1783
	 * @return bool
1784
	 */
1785
	public function enable($recursive = true) {
1786
		$guid = (int) $this->guid;
1787
		if (!$guid) {
1788
			return false;
1789
		}
1790
		
1791
		if (!_elgg_services()->hooks->getEvents()->trigger('enable', $this->type, $this)) {
1792
			return false;
1793
		}
1794
		
1795
		if (!$this->canEdit()) {
1796
			return false;
1797
		}
1798
		
1799
		// Override access only visible entities
1800
		$old_access_status = access_get_show_hidden_status();
1801
		access_show_hidden_entities(true);
1802
1803
		$db = $this->getDatabase();
1804
		$result = $db->updateData("
1805
			UPDATE {$db->prefix}entities
1806
			SET enabled = 'yes'
1807
			WHERE guid = $guid
1808
		");
1809
1810
		$this->deleteMetadata('disable_reason');
1811
		$this->enableMetadata();
1812
		$this->enableAnnotations();
1813
1814
		if ($recursive) {
1815
			$disabled_with_it = elgg_get_entities_from_relationship([
1816
				'relationship' => 'disabled_with',
1817
				'relationship_guid' => $guid,
1818
				'inverse_relationship' => true,
1819
				'limit' => 0,
1820
			]);
1821
1822
			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...
1823
				$e->enable();
1824
				remove_entity_relationship($e->guid, 'disabled_with', $guid);
1825
			}
1826
		}
1827
	
1828
		access_show_hidden_entities($old_access_status);
1829
	
1830 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...
1831
			$this->attributes['enabled'] = 'yes';
1832
			_elgg_services()->hooks->getEvents()->trigger('enable:after', $this->type, $this);
1833
		}
1834
1835
		return $result;
1836
	}
1837
1838
	/**
1839
	 * Is this entity enabled?
1840
	 *
1841
	 * @return boolean Whether this entity is enabled.
1842
	 */
1843
	public function isEnabled() {
1844
		return $this->enabled == 'yes';
1845
	}
1846
1847
	/**
1848 1
	 * Deletes the entity.
1849 1
	 *
1850
	 * Removes the entity and its metadata, annotations, relationships,
1851
	 * river entries, and private data.
1852
	 *
1853
	 * Optionally can remove entities contained and owned by this entity.
1854
	 *
1855
	 * @warning If deleting recursively, this bypasses ownership of items contained by
1856
	 * the entity.  That means that if the container_guid = $this->guid, the item will
1857
	 * be deleted regardless of who owns it.
1858
	 *
1859
	 * @param bool $recursive If true (default) then all entities which are
1860
	 *                        owned or contained by $this will also be deleted.
1861
	 *
1862
	 * @return bool
1863
	 */
1864
	public function delete($recursive = true) {
1865
1866
		$guid = $this->guid;
1867
		if (!$guid) {
1868
			return false;
1869 1
		}
1870
		
1871 1
		// first check if we can delete this entity
1872 1
		// NOTE: in Elgg <= 1.10.3 this was after the delete event,
1873
		// which could potentially remove some content if the user didn't have access
1874
		if (!$this->canDelete()) {
1875
			return false;
1876
		}
1877
1878
		// now trigger an event to let others know this entity is about to be deleted
1879 1
		// so they can prevent it or take their own actions
1880
		if (!_elgg_services()->hooks->getEvents()->trigger('delete', $this->type, $this)) {
1881
			return false;
1882
		}
1883
1884
		if ($this instanceof ElggUser) {
1885 1
			// ban to prevent using the site during delete
1886
			_elgg_services()->usersTable->markBanned($this->guid, true);
1887
		}
1888
1889 1
		// Delete contained owned and otherwise releated objects (depth first)
1890
		if ($recursive) {
1891
			// Temporarily overriding access controls
1892
			$entity_disable_override = access_get_show_hidden_status();
1893
			access_show_hidden_entities(true);
1894
			$ia = elgg_set_ignore_access(true);
1895 1
1896
			// @todo there was logic in the original code that ignored
1897 1
			// entities with owner or container guids of themselves.
1898 1
			// this should probably be prevented in \ElggEntity instead of checked for here
1899 1
			$base_options = [
1900
				'wheres' => [
1901
					"e.guid != $guid",
1902
				],
1903
				'limit' => false,
1904
			];
1905 1
			
1906 1
			foreach (['owner_guid', 'container_guid'] as $db_column) {
1907
				$options = $base_options;
1908
				$options[$db_column] = $guid;
1909
				
1910
				$batch = new \ElggBatch('elgg_get_entities', $options);
1911 1
				$batch->setIncrementOffset(false);
1912 1
				
1913 1
				/* @var $e \ElggEntity */
1914
				foreach ($batch as $e) {
1915 1
					$e->delete(true);
1916 1
				}
1917
			}
1918
			
1919 1
			access_show_hidden_entities($entity_disable_override);
1920 1
			elgg_set_ignore_access($ia);
1921
		}
1922
1923
		$entity_disable_override = access_get_show_hidden_status();
1924 1
		access_show_hidden_entities(true);
1925 1
		$ia = elgg_set_ignore_access(true);
1926
		
1927
		// Now delete the entity itself
1928 1
		$this->deleteMetadata();
1929 1
		$this->deleteOwnedMetadata();
1930 1
		$this->deleteAnnotations();
1931
		$this->deleteOwnedAnnotations();
1932
		$this->deleteRelationships();
1933 1
		$this->deleteAccessCollectionMemberships();
1934 1
		$this->deleteOwnedAccessCollections();
1935 1
1936 1
		access_show_hidden_entities($entity_disable_override);
1937 1
		elgg_set_ignore_access($ia);
1938 1
1939 1
		elgg_delete_river(['subject_guid' => $guid, 'limit' => false]);
1940
		elgg_delete_river(['object_guid' => $guid, 'limit' => false]);
1941 1
		elgg_delete_river(['target_guid' => $guid, 'limit' => false]);
1942 1
		
1943
		remove_all_private_settings($guid);
1944 1
1945 1
		_elgg_invalidate_cache_for_entity($guid);
1946 1
		_elgg_invalidate_memcache_for_entity($guid);
1947
1948 1
		$dbprefix = _elgg_config()->dbprefix;
1949
		
1950 1
		$sql = "
1951 1
			DELETE FROM {$dbprefix}entities
1952
			WHERE guid = :guid
1953 1
		";
1954
		$params = [
1955
			':guid' => $guid,
1956 1
		];
1957
1958
		$deleted = $this->getDatabase()->deleteData($sql, $params);
1959
1960 1
		if ($deleted && in_array($this->type, ['object', 'user', 'group', 'site'])) {
1961
			// delete from type-specific subtable
1962
			$sql = "
1963 1
				DELETE FROM {$dbprefix}{$this->type}s_entity
1964
				WHERE guid = :guid
1965 1
			";
1966
			$this->getDatabase()->deleteData($sql, $params);
1967
		}
1968 1
		
1969
		_elgg_clear_entity_files($this);
1970
1971 1
		return (bool) $deleted;
1972
	}
1973
1974 1
	/**
1975
	 * {@inheritdoc}
1976 1
	 */
1977 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...
1978
		$object = $this->prepareObject(new \stdClass());
1979
		$params = ['entity' => $this];
1980
		$object = _elgg_services()->hooks->trigger('to:object', 'entity', $params, $object);
1981
		return $object;
1982 2
	}
1983 2
1984 2
	/**
1985 2
	 * Prepare an object copy for toObject()
1986 2
	 *
1987
	 * @param \stdClass $object Object representation of the entity
1988
	 * @return \stdClass
1989
	 */
1990
	protected function prepareObject($object) {
1991
		$object->guid = $this->guid;
1992
		$object->type = $this->getType();
1993
		$object->subtype = $this->getSubtype();
1994
		$object->owner_guid = $this->getOwnerGUID();
1995 2
		$object->container_guid = $this->getContainerGUID();
1996 2
		$object->time_created = date('c', $this->getTimeCreated());
1997 2
		$object->time_updated = date('c', $this->getTimeUpdated());
1998 2
		$object->url = $this->getURL();
1999 2
		$object->read_access = (int) $this->access_id;
2000 2
		return $object;
2001 2
	}
2002 2
2003 2
	/*
2004 2
	 * LOCATABLE INTERFACE
2005 2
	 */
2006
2007
	/**
2008
	 * Gets the 'location' metadata for the entity
2009
	 *
2010
	 * @return string The location
2011
	 */
2012
	public function getLocation() {
2013
		return $this->location;
2014
	}
2015
2016
	/**
2017
	 * Sets the 'location' metadata for the entity
2018
	 *
2019
	 * @param string $location String representation of the location
2020
	 *
2021
	 * @return void
2022
	 */
2023
	public function setLocation($location) {
2024
		$this->location = $location;
2025
	}
2026
2027
	/**
2028
	 * Set latitude and longitude metadata tags for a given entity.
2029
	 *
2030
	 * @param float $lat  Latitude
2031
	 * @param float $long Longitude
2032
	 *
2033
	 * @return void
2034
	 * @todo Unimplemented
2035
	 */
2036
	public function setLatLong($lat, $long) {
2037
		$this->{"geo:lat"} = $lat;
2038
		$this->{"geo:long"} = $long;
2039
	}
2040
2041 1
	/**
2042 1
	 * Return the entity's latitude.
2043 1
	 *
2044 1
	 * @return float
2045
	 * @todo Unimplemented
2046
	 */
2047
	public function getLatitude() {
2048
		return (float) $this->{"geo:lat"};
2049
	}
2050
2051
	/**
2052 1
	 * Return the entity's longitude
2053 1
	 *
2054
	 * @return float
2055
	 * @todo Unimplemented
2056
	 */
2057
	public function getLongitude() {
2058
		return (float) $this->{"geo:long"};
2059
	}
2060
2061
	/*
2062 1
	 * SYSTEM LOG INTERFACE
2063 1
	 */
2064
2065
	/**
2066
	 * Return an identification for the object for storage in the system log.
2067
	 * This id must be an integer.
2068
	 *
2069
	 * @return int
2070
	 */
2071
	public function getSystemLogID() {
2072
		return $this->getGUID();
2073
	}
2074
2075
	/**
2076
	 * For a given ID, return the object associated with it.
2077
	 * This is used by the system log. It can be called on any Loggable object.
2078
	 *
2079
	 * @param int $id GUID.
2080
	 * @return int GUID
2081
	 */
2082
	public function getObjectFromID($id) {
2083
		return get_entity($id);
2084
	}
2085
2086
	/**
2087
	 * Returns tags for this entity.
2088
	 *
2089
	 * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}.
2090
	 *
2091
	 * @param array $tag_names Optionally restrict by tag metadata names.
2092
	 *
2093
	 * @return array
2094
	 */
2095
	public function getTags($tag_names = null) {
2096
		if ($tag_names && !is_array($tag_names)) {
2097
			$tag_names = [$tag_names];
2098
		}
2099
2100
		$valid_tags = elgg_get_registered_tag_metadata_names();
2101
		$entity_tags = [];
2102
2103
		foreach ($valid_tags as $tag_name) {
2104
			if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
2105
				continue;
2106
			}
2107
2108
			if ($tags = $this->$tag_name) {
2109
				// if a single tag, metadata returns a string.
2110
				// if multiple tags, metadata returns an array.
2111
				if (is_array($tags)) {
2112
					$entity_tags = array_merge($entity_tags, $tags);
2113
				} else {
2114
					$entity_tags[] = $tags;
2115
				}
2116
			}
2117
		}
2118
2119
		return $entity_tags;
2120
	}
2121
	
2122
	/**
2123
	 * Remove the membership of all access collections for this entity (if the entity is a user)
2124
	 *
2125
	 * @return bool
2126
	 * @since 1.11
2127
	 */
2128
	public function deleteAccessCollectionMemberships() {
2129
	
2130
		if (!$this->guid) {
2131
			return false;
2132
		}
2133 1
		
2134
		if ($this->type !== 'user') {
2135 1
			return true;
2136
		}
2137
		
2138
		$ac = _elgg_services()->accessCollections;
2139 1
		
2140 1
		$collections = $ac->getCollectionsByMember($this->guid);
2141
		if (empty($collections)) {
2142
			return true;
2143
		}
2144
		
2145
		$result = true;
2146
		foreach ($collections as $collection) {
2147
			$result = $result & $ac->removeUser($this->guid, $collection->id);
2148
		}
2149
		
2150
		return $result;
2151
	}
2152
	
2153
	/**
2154
	 * Remove all access collections owned by this entity
2155
	 *
2156
	 * @return bool
2157
	 * @since 1.11
2158
	 */
2159
	public function deleteOwnedAccessCollections() {
2160
		
2161
		if (!$this->guid) {
2162
			return false;
2163
		}
2164 1
		
2165
		$ac = _elgg_services()->accessCollections;
2166 1
		
2167
		$collections = $ac->getEntityCollections($this->guid);
2168
		if (empty($collections)) {
2169
			return true;
2170 1
		}
2171
		
2172 1
		$result = true;
2173 1
		foreach ($collections as $collection) {
2174 1
			$result = $result & $ac->delete($collection->id);
2175
		}
2176
		
2177
		return $result;
2178
	}
2179
2180
	/**
2181
	 * Update the last_action column in the entities table.
2182
	 *
2183
	 * @warning This is different to time_updated.  Time_updated is automatically set,
2184
	 * while last_action is only set when explicitly called.
2185
	 *
2186
	 * @param int $posted Timestamp of last action
2187
	 * @return int|false
2188
	 * @access private
2189
	 */
2190
	public function updateLastAction($posted = null) {
2191
		$posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
2192
		if ($posted) {
2193
			$this->attributes['last_action'] = $posted;
2194
			_elgg_services()->entityCache->set($this);
2195 2
			$this->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
2196 2
		}
2197 2
		return $posted;
2198 2
	}
2199
}
2200